<?xml version="1.0" encoding="UTF-8"?><d:tdl xmlns="http://www.w3.org/1999/xhtml" xmlns:b="http://www.backbase.com/2006/btl" xmlns:bb="http://www.backbase.com/2006/client"  xmlns:d="http://www.backbase.com/2006/tdl" >

	<d:namespace name="http://www.backbase.com/2006/btl">

		<d:uses element="element" src="../element/element.xml"/>
		<d:uses interface="iPageable" src="../iPageable/iPageable.xml"/>

		<d:interface name="iDataCommunicator">
			

			<d:method name="sendRequest">
				
				<d:argument name="action">
				
				</d:argument>
				<d:argument name="observer">
					
				</d:argument>
			</d:method>
		</d:interface>

		<!-- ############################################# dataSource ################################## -->
		<d:element name="dataSource" extends="b:element"><!--  b:staticData commented out because of bug N7352 -->
			

			<d:resource type="text/javascript"><![CDATA[if (!btl.dataBinding) {
	/**
	 * <p>The btl.dataBinding API object.</p>
	 * <p>On this object you will find data binding shared API functions.</p>
	 * @publish
	 */
	btl.dataBinding = {};

	/**
	 * A list of DataSources
	 * @publish
	 */
	btl.dataBinding.DataSourceList = function() {
		this.sources = {};
		this.availableObservers = []; // observers that have not been attached to a source yet
		this.lastIndex = 0;
	}
	btl.dataBinding.DataSourceList.prototype = {};

	btl.dataBinding.DataSourceList.prototype.getSource = function(sName) {
		return this.sources[sName] || null;
	}

	btl.dataBinding.DataSourceList.prototype.getUniqueSourceName = function() {
		return 'BtlDataSource_' + this.lastIndex++;
	}

	btl.dataBinding.DataSourceList.prototype.registerSource = function(oDataSource) {
		var sName = oDataSource.getProperty('name');
		this.sources[sName] = oDataSource;

		// refresh all registered observers
		var aObservers = oDataSource.getProperty('observers');
		for (var i = 0, iMax = aObservers.length; i < iMax; i++)
			aObservers[i].refresh();

		// retry all unregistered observers
		for (var i = 0, len = this.availableObservers.length; i < len; i++)
			this.registerObserver(this.availableObservers.shift());
	}

	btl.dataBinding.DataSourceList.prototype.removeSource = function(oDataSource) {
		var sName = oDataSource.getProperty('name');
		if (sName in this.sources) {
			delete this.sources[sName];
			var aObservers = oDataSource.getProperty('observers').slice(0);
			// remove observers, and attempt to re-register them...
			for (var i = 0, iLimit = aObservers.length; iLimit > i; i++)
				this.registerObserver(oDataSource.removeObserver(aObservers[i]));
		}
	}

	btl.dataBinding.DataSourceList.prototype.addAvailableObserver = function(oObserver) {
		for (var i = 0, len = this.availableObservers.length; i < len; i++)
			if (this.availableObservers[i] == oObserver)
				return; // don't add if already in list
		this.availableObservers.push(oObserver);
	}

	btl.dataBinding.DataSourceList.prototype.removeAvailableObserver = function(oObserver) {
		for (var i = 0, len = this.availableObservers.length; i < len; i++)
			if (this.availableObservers[i] == oObserver)
				return this.availableObservers.splice(i, 1)[0];
	}

	btl.dataBinding.DataSourceList.prototype.registerObserver = function(oObserver) {
		var oDataSource = oObserver.getDataSource();

		if (oDataSource) {
			oDataSource.addObserver(oObserver);
			this.removeAvailableObserver(oObserver);
		} else {
			// datasource not created yet or not initialized
			this.addAvailableObserver(oObserver);
		}
	}

	btl.dataBinding.DataSourceList.prototype.unregisterObserver = function(oObserver) {
		var oSource = oObserver.getProperty('dataSource');
		if (oSource)
			oSource.removeObserver(oObserver);
		this.removeAvailableObserver(oObserver);
	}

	btl.dataBinding.dataSources = new btl.dataBinding.DataSourceList();


	btl.dataBinding.getSource = function btl_dataBinding_getSource(sName) {
		// XXX: for backwards compatibility
		return sName && sName.length ? btl.dataBinding.dataSources.getSource(sName) : null;
	}

	btl.dataBinding.registerSource = function btl_dataBinding_registerSource(oDataSource){
		btl.dataBinding.dataSources.registerSource(oDataSource);
	}

	btl.dataBinding.removeSource = function btl_dataBinding_removeSource(oDataSource) {
		btl.dataBinding.dataSources.removeSource(oDataSource);
	}

	btl.dataBinding.registerObserver = function btl_dataBinding_registerObserver(oObserver) {
		btl.dataBinding.dataSources.registerObserver(oObserver);
	}

	//get data
	btl.dataBinding.refreshObserver = function btl_dataBinding_refreshObserver(oObserver, bClear) {
		btl.dataSource.actionRequest(oObserver, 'read', {'attributes': oObserver.getProperty('state')}, bClear);
	}

	btl.dataBinding.acceptData = function btl_dataBinding_acceptData(oDataSource, oActionGroup, oObserver) {
		var oEvent = bb.document.createEvent('Events');
		oEvent.initEvent('dataUpdate', false, false);
		oEvent.data = oActionGroup;
		oEvent.dataObserver = oObserver;
		oDataSource.dispatchEvent(oEvent);

		var aActions = oActionGroup.getActionList();

		for (var i = 0; i < aActions.length; i++) {
			var oAction = aActions[i];
			var oRecords = oAction.records;

			// throw error event if there is an error attribute
			if (oAction.hasAttribute('error'))
				btl.dataBinding.fireErrorEvent(oDataSource, 0, oAction.getAttribute('error'));

			switch (oAction.type) {
				case 'error': // error message was already shown
					continue;
				case 'sort':
				case 'read':
					// build row indexes, use indexes attribute if available
					var aIndexes = oAction.getAttribute('indexes') || oRecords.getRecordIds();

					// add records to record cache
					// remove record IDs from aIndexes that are not known
					var aNewIndexes = [];
					for (var j = 0, iMax = aIndexes.length; j < iMax; j++) {
						var sIndex = aIndexes[j];
						if (oRecords.hasRecord(sIndex)) {
							oDataSource._.oRecords.addRecord(oRecords.getRecord(sIndex));
							aNewIndexes[aNewIndexes.length] = sIndex;
						} else if (oDataSource._.oRecords.hasRecord(sIndex)) {
							aNewIndexes[aNewIndexes.length] = sIndex;
						}
					}
					aIndexes = aNewIndexes;

					oDataSource._['_totalRecords'] = oAction.hasAttribute('totalRecords') ?
							oAction.getAttribute('totalRecords') : aIndexes.length;
					oDataSource._['_records'] = aIndexes;
					btl.dataSource.actionApply(oDataSource, oAction.type, aIndexes, oObserver);
					oDataSource.updateObservers(oAction.type, aIndexes, oObserver, oAction);
					break;
				case 'create':
				case 'delete':
				case 'update':
					// indexes whose updates are confirmed
					var aIndexes = oAction.getAttribute('records') || oRecords.getRecordIds() || [];

					if (oAction.hasAttribute('totalRecords')) {
						oDataSource._['_totalRecords'] = oAction.getAttribute('totalRecords');
					} else {
						if (oAction.type == 'create')
							oDataSource._['_totalRecords'] += aIndexes.length;
						else if (oAction.type == 'delete')
							oDataSource._['_totalRecords'] -= aIndexes.length;
					}
					btl.dataSource.actionApply(oDataSource, oAction.type, oRecords.size ? oRecords : aIndexes, oObserver);
					// delete, update and create are for all observers
					oDataSource.updateObservers(oAction.type, aIndexes, null, oAction);
					break;
				default:
					//it also fires actionResponse event
					btl.dataSource.actionApply(oDataSource, oAction.type, oAction, oObserver);
					var aIndexes = oAction.getAttribute('indexes') || null;
					oDataSource.updateObservers(oAction.type, aIndexes, oObserver, oAction);
			}
		}//for
		oDataSource._.oRecords.clearChangedRecords();
		oDataSource._.oRecords.prune(oDataSource.getProperty('observers'));
	}

	btl.dataBinding.fireErrorEvent = function btl_dataBinding_fireErrorEvent(oSource, errorCode, errorText) {
		var oEvent = bb.document.createEvent('Events');
		oEvent.initEvent('error', true, false);
		oEvent.message = errorText;
		oEvent.code = errorCode;
		oSource.dispatchEvent(oEvent);
		return oEvent;
	}

	btl.dataBinding.setType = function btl_dataBinding_setType(oDataSource, sType) {
		var oTypeHandler = btl.dataSource.types.getType(sType);
		if (!oTypeHandler) {
			bb.command.trace(oDataSource, 'Data type: ' + sType + ' is not supported.');
			return false;
		}
		oDataSource.dataType = sType;
		oDataSource.typeHandler = oTypeHandler;
		return true;
	}

	// Returns unformatted value directly from a record which possibly is not in a dataSource record pool
	btl.dataBinding.getRecordValue = function btl_dataSource_getRecordValue(oSource, oRecord, oDataField) {
		return oSource.typeHandler.getValue(oRecord, oDataField);
	}


	/**
	 * A list of Sorters.
	 * @publish
	 */
	btl.dataBinding.SorterList = function() {
		this.sorters = [];

		this.addSorter('default', new btl.dataBinding.DefaultSorter());
		this.addSorter('string',  new btl.dataBinding.StringSorter());
		this.addSorter('number',  new btl.dataBinding.NumberSorter());
		this.addSorter('date',    new btl.dataBinding.DateSorter());
	}
	btl.dataBinding.SorterList.prototype = {};

	btl.dataBinding.SorterList.prototype.addSorter = function(sName, oSorter) {
		this.sorters[sName] = oSorter;
		this[sName] = oSorter;	// XXX: for backwards compatibility
		return oSorter;
	}

	btl.dataBinding.SorterList.prototype.getSorter = function(sName) {
		return this.sorters[sName] || null;
	}

	btl.dataBinding.SorterList.prototype.removeSorter = function(sName) {
		var oSorter = this.getSorter(sName);
		if (oSorter) {
			delete this.sorters[sName];
			delete this[sName];
		}
		return oSorter;
	}

	/**
	 * Sorts an array of objects by their value property.
	 * @param {Array} aArray Array containing objects with value property to sort on.
	 * @param {String} sType Sorting type (optional).
	 * @param {Boolean} bDescend The sorting direction (optional).
	 * @return Sorted array.
	 */
	btl.dataBinding.SorterList.prototype.sortObjectArray = function(aArray, sType, bDescend) {
		var oSorter = sType && btl.dataBinding.sorters.getSorter(sType) ||
				btl.dataBinding.sorters.getSorter('default');

		return oSorter.sortObjectArray(aArray, bDescend);
	}



	/**
	 * An object implementing a sorting method
	 * @publish
	 */
	btl.dataBinding.Sorter = function() {
		this.sortDirection = 1; // 1 ascending, -1 descending
	}
	btl.dataBinding.Sorter.prototype = {};

	btl.dataBinding.Sorter.prototype.compare = function(a, b) {
		// implement
	}

	/**
	 * Sorts an array of objects by their value property.
	 * @param {Array} aArray Array containing objects with value property to sort on.
	 * @param {Boolean} bDescend The sorting direction (optional).
	 * @return Sorted array.
	 */
	btl.dataBinding.Sorter.prototype.sortObjectArray = function(aArray, bDescend) {
		var oThis = this;
		this.sortDirection = bDescend ? -1 : 1;

		aArray.sort(function(a, b) {
				return oThis.compare(a.value, b.value);
			});

		return aArray;
	}



	/**
	 * @publish
	 */
	btl.dataBinding.DefaultSorter = function() {
		btl.dataBinding.Sorter.call(this);
	}
	btl.dataBinding.DefaultSorter.prototype = new btl.dataBinding.Sorter();

	btl.dataBinding.DefaultSorter.prototype.compareNumberRegExp = /^[\.0-9]+$/;

	btl.dataBinding.DefaultSorter.prototype.compare = function(a, b) {
		var oValue1 = a;
		var oValue2 = b;
		var iType = 0;

		if (this.compareNumberRegExp.test(oValue1)) {
			oValue1 = parseFloat(oValue1);
			iType = 1;
		}
		if (this.compareNumberRegExp.test(oValue2)) {
			oValue2 = parseFloat(oValue2);
			iType += 2;
		}

		// Always put numbers in front of alphabetic characters (or reversed)
		if (iType == 3)
			return (oValue1 - oValue2) * this.sortDirection;
		if (iType == 2)
			return this.sortDirection;
		if (iType == 1)
			return -this.sortDirection;

		if (oValue1 < oValue2)
			return -this.sortDirection;
		else if (oValue1 > oValue2)
			return this.sortDirection;
		else
			return 0;
	}


	/**
	 * @publish
	 */
	btl.dataBinding.StringSorter = function() {
		btl.dataBinding.Sorter.call(this);
	}
	btl.dataBinding.StringSorter.prototype = new btl.dataBinding.Sorter();

	btl.dataBinding.StringSorter.prototype.compare = function(a, b) {
		var oValue1 = String(a).toLowerCase();
		var oValue2 = String(b).toLowerCase();
		if (oValue1 < oValue2)
			return -this.sortDirection;
		else if (oValue1 > oValue2)
			return this.sortDirection;
		else
			return 0;
	}


	/**
	 * @publish
	 */
	btl.dataBinding.NumberSorter = function() {
		btl.dataBinding.Sorter.call(this);
	}
	btl.dataBinding.NumberSorter.prototype = new btl.dataBinding.Sorter();

	btl.dataBinding.NumberSorter.prototype.compare = function(a, b) {
		var iType = 0;
		var oValue1 = parseFloat(a);
		if (isNaN(oValue1))
			iType = 1;

		var oValue2 = parseFloat(b);
		if (isNaN(oValue2))
			iType += 2;
		if (iType == 1)
			return this.sortDirection;
		if (iType == 2)
			return -this.sortDirection;

		return (oValue1 - oValue2) * this.sortDirection;
	}


	/**
	 * @publish
	 */
	btl.dataBinding.DateSorter = function() {
		btl.dataBinding.Sorter.call(this);
	}
	btl.dataBinding.DateSorter.prototype = new btl.dataBinding.Sorter();

	btl.dataBinding.DateSorter.prototype.compareIntRegExp = /^[0-9]+$/;

	btl.dataBinding.DateSorter.prototype.compare = function(a, b) {
		var oValue1 = this.compareIntRegExp.test(a) ? a : (new Date(a)).valueOf();
		if (isNaN(oValue1))
			oValue1 = (new Date()).valueOf();
		var oValue2 = this.compareIntRegExp.test(b) ? b : (new Date(b)).valueOf();
		if (isNaN(oValue2))
			oValue2 = (new Date()).valueOf();
		return (oValue1 - oValue2) * this.sortDirection;
	}

	btl.dataBinding.sorters = new btl.dataBinding.SorterList();

}

if (!btl.dataSource) {
	/**
	 * <p>The btl.dataSource API object.</p>
	 * <p>On this object, you will find data source API functions.</p>
	 * @publish
	*/
	btl.dataSource = {};

	/**
	 * Retrieves an array of formatters for the specified query. If the optional format argument
	 * is provided, it will also be parsed and included into the resulting array.
	 * @param {Element} source Reference to the dataSource element.
	 * @param {String} query Query string.
	 * @param {String} format Optional additional formatter.
	 * @return {Array} Formatters array.
	 * @publish
	 */
	btl.dataSource.getFormat = function btl_dataSource_getFormat(oSource, sQuery, vFormat){
		var aFormats = [];
		var vFieldFormat = btl.dataSource.getDataField(oSource, sQuery).format;
		if (vFieldFormat instanceof Array) {
			for (var i=0; vFieldFormat.length > i; i++)
				aFormats[i] = vFieldFormat[i];
		} else if (vFieldFormat) {
			aFormats = [vFieldFormat];
		}
		if (vFormat && vFormat.length){ //parse and append formats
			var arr = vFormat.split(']');
			for (var i=0; arr.length > i; i++){
			   if (arr[i].length){
					var arr2 = arr[i].split('[', 2);
					aFormats = aFormats.concat(arr2[0].replace(/^\s+/, '').split(/\s+/))
					if(arr2.length > 1){
						aFormats[aFormats.length-1] = aFormats[aFormats.length-1] + '['+arr2[1]+ ']'
					}
			   }//if
			}//for
		}//if
		return aFormats;
	}

	/**
	 * Retrieves a field value from the record that is specified by the id parameter.
	 * @param {Element} source Reference to the dataSource element.
	 * @param {String} id Record identifier.
	 * @param {String} query Query string.
	 * @param {Array} format Optional formats array.
	 *                       OR a string (single formatter to apply)
	 *                       OR false (no formatters are applied, not even defaults)
	 * @return {Variant} Field value.
	 * @publish
	 */
	btl.dataSource.getValue = function btl_dataSource_getValue(oSource, sId, sQuery, vFormat) {
		var oQuery = btl.dataSource.getDataField(oSource, sQuery);
		var oRecord = oSource._.oRecords.getRecord(sId);

		if ((!vFormat || !vFormat.length) && vFormat !== false && oQuery.format)
			vFormat = oQuery.format;
		var aFormat = !vFormat ? [] : vFormat instanceof Array ? vFormat : [vFormat];

		// XXX: check whether oRecord == null
		var sValue = oRecord.getFieldValue(oQuery);

		if (aFormat.length) {
			var sOriginalValue = sValue; //unformatted value
			var vRawValue,
				bRawDefined = false;
			for (var i = 0; i < aFormat.length; i++) {
			 	if (aFormat[i].length) {
			 		var oParsed = btl.dataBinding.formatters.parseFormatterString(aFormat[i]);
					var oFormatter = btl.dataBinding.formatters.getFormatter(oParsed.name);
					if (oFormatter) {
						if (oFormatter.raw) {
							if (!bRawDefined) {
								bRawDefined = true;
								vRawValue = oRecord.getSourceFieldValue(oQuery, true);
							}
							sValue = oFormatter.format(sValue, vRawValue, oParsed.parameters);
						} else
							sValue = oFormatter.format(sValue, sOriginalValue, oParsed.parameters);
					} else
						bb.command.trace(oSource, "Formatter " + oParsed.name + " is not found");
			 	}
			}
		}
		return sValue;
	}

	/**
	 * Sets a field value on the record that is specified by the id parameter.
	 * @param {Element} source Reference to the dataSource element.
	 * @param {String} id Record identifier.
	 * @param {String} query Query string.
	 * @param {Variant} value The value to be set.
	 * @return {String} The changed value of the field.
	 * @publish
	 */
	btl.dataSource.setValue = function btl_dataSource_setValue(oSource, sId, sQuery, vValue) {
		var oRecord = oSource._.oRecords.getRecord(sId);
		if (!oRecord) {
			bb.command.trace(oSource, "No such record is registered (id: " + sId + ")", 4);
			return;
		}
		var oQuery = btl.dataSource.getDataField(oSource, sQuery);
		oRecord.setFieldValue(oQuery, vValue);

		return vValue;
	}

	/**
	 * Returns a list of records related to the record that is specified by the id parameter.
	 * @param {Element} source Reference to the dataSource element.
	 * @param {String} id Record identifier.
	 * @param {String} fieldName The qualified field name that connects the records.
	 * @publish
	 */
	btl.dataSource.getLinked = function btl_dataSource_getLinked(oSource, sId, sField) {
		var arr = [];

		if (!('getRecords' in oSource.typeHandler)) {
			bb.command.trace(oSource, "Data type (" + oSource.dataType + ") does not support getRecords method.", 4);
			return arr;
		}
		if (btl.dataSource.getDataField(oSource, sField).dataSchema == '_self') { //get list of children of the record
			var oQuery = btl.dataSource.getDataField(oSource, sField);
			var oRecords = oSource.typeHandler.getRecords(oSource._.oRecords.getRecord(sId).source, oQuery);

			var oIdField = btl.dataSource.getDataField(oSource, 'identifier');

			for (var sName in oRecords) {
				if (oRecords.hasOwnProperty(sName)) {
					var sOrigRecId = oIdField.select ? btl.dataBinding.getRecordValue(oSource, oRecords[sName], oIdField) : sName;
					if (typeof sInd == 'string' && sInd == '') {
						btl.dataBinding.fireErrorEvent(oSource, 0, "Record id is empty");
						bb.command.trace(oSource, "Record id is empty");
					} else {
						var sRecId = oIdField.select ? sOrigRecId : sId + "." + sOrigRecId; //generated qualified record id or provided unique one
						oSource._.oRecords.addRecord(new btl.dataBinding.Record(oRecords[sName], oSource.typeHandler, sRecId, oIdField));
						arr[arr.length] = sRecId;
					}
				}
			}
		}
		return arr;
	}

	/**
	 * Performs an action request.
	 * @param {Object} source Reference to a dataObserver or dataSource controller object. If a dataSource element is passed, all its observers will be refreshed.
	 * @param {String} action Action to perform. In addition to the standard actions, a user can define custom actions.
	 * @paramvalue {action} read Reads elements from the datasource.
	 * @paramvalue {action} sort Sorts datasource elements.
	 * @paramvalue {action} update Updates datasource elements.
	 * @paramvalue {action} create Creates elements in the datasource.
	 * @paramvalue {action} delete Deletes elements from the datasource.
	 * @param {Object} parameters Optional parameters.
	 * @param {Boolean} clear The observer should be cleared.
	 * @example btl:btl_custom_actionRequest
	 * @return {Void} Nothing
	 * @publish
	 */
	btl.dataSource.actionRequest = function btl_dataSource_actionRequest(oController, sAction, vParameters, bClear) {
		if (!oController || !oController._)
			return;//destroyed

		var bIsObserver = bb.instanceOf(oController, btl.namespaceURI, 'dataObserver');
		var oSource = bIsObserver ? oController.getProperty('dataSource') : oController;
		if (!oSource  || !oSource._.bInitialized || !bb.instanceOf(oSource, btl.namespaceURI, 'iDataCommunicator'))
			return;

		// un-tangle variable parameters argument
		var vRecords, hAttributes;
		if (vParameters instanceof Object && !(vParameters instanceof Array)) {
			vRecords = vParameters.records || null;
			hAttributes = vParameters.attributes || {};
		} else {
			vRecords = vParameters;
			hAttributes = {};
		}
		if (vRecords instanceof Array && sAction != 'update' && vRecords[0] && (typeof vRecords[0] != 'object' || vRecords[0] instanceof String)) {
			hAttributes.records = vRecords;
			vRecords = null;
		}

		//Queue the request, other request might be busy...
		oSource._._requestQueue.push({
				isObserver: bIsObserver,
				controller: oController,
				action: sAction,
				parameters: vParameters,
				records: vRecords,
				attributes: hAttributes,
				clear: bClear
			});
		if (oSource._._requestQueue.length == 1)
			btl.dataSource.actionRequestQueued(oSource, bIsObserver, oController, sAction,
											vParameters, vRecords, hAttributes, bClear);
	}

	//Private functions
	btl.dataSource.requestQueueContinue = function btl_dataSource_requestQueueContinue(oSource) {
		oSource._._requestQueue.shift();
		if (oSource._._requestQueue.length) {
			var oSync        = oSource._._requestQueue[0];
			btl.dataSource.actionRequestQueued(oSource, oSync.isObserver, oSync.controller, oSync.action,
											oSync.parameters, oSync.records, oSync.attributes, oSync.clear);
		}
	}

	//Private functions
	btl.dataSource.actionRequestQueued = function btl_dataSource_actionRequestQueued(oSource, bIsObserver, oController, sAction, vParameters, vRecords, hAttributes, bClear) {
		if (bClear)
			oController.setProperty('indexes', []);

		if (!bIsObserver && (sAction == 'read' || sAction == 'sort')) {
			btl.dataBinding.fireErrorEvent(oSource, 0, 'Read action must be requested for one observer only.');
			bb.command.trace(oController, 'Read action must be requested for one observer only.', 2);
			return;
		}

		if (bIsObserver) {
	 		var oEvent = bb.document.createEvent('Events');
			oEvent.initEvent('actionRequest', false, false);
			oEvent.action = sAction;
			oEvent.data = vParameters;
			oController.dispatchEvent(oEvent);
		}

		var oJSONTypeHandler = btl.dataSource.types.getType('application/json');

		var oRecords = new btl.dataBinding.RecordList();
		var oAction = new btl.dataBinding.Action(sAction, oRecords);
		switch (sAction) {
			case 'update':
				var aUpdatedIds; // will contain list of record IDs that have outgoing changes (updated)
				if (vRecords) {
					// if records attribute passed, filter out records that have not changed
					aUpdatedIds = [];
					for (var i = 0, iMax = vRecords.length; i < iMax; i++) {
		 				var oRecord = oSource._.oRecords.getRecord(vRecords[i]);
		 				if (oRecord && oRecord.updated)
							aUpdatedIds[aUpdatedIds.length] = vRecords[i];
		 			}
				} else {
					// if no records attribute passed, update all records that have changed
					aUpdatedIds = oSource._.oRecords.getUpdatedRecordIds();
				}
				if (!aUpdatedIds.length) {
					// continue with next request if aList is empty (no outgoing changes)
					btl.dataSource.requestQueueContinue(oSource);
					return;
				}
				for (var i = 0, iLimit = aUpdatedIds.length; i < iLimit; i++) {
					var oRecord = oSource._.oRecords.getRecord(aUpdatedIds[i]);
					oRecords.addRecord(oRecord);
				}
				break;
			default:
				var oIdField = btl.dataSource.getDataField(oSource, 'identifier');
				if (vRecords) {
					if (vRecords instanceof Array)
						oRecords.serializeAsArray = true;
					for (var sId in vRecords) {
						if (vRecords.hasOwnProperty(sId)) {
							var oRecord = new btl.dataBinding.Record({}, oJSONTypeHandler, sId, null);
							for (var sFieldName in vRecords[sId]) {
								if (vRecords[sId].hasOwnProperty(sFieldName)) {
									var oDataField = btl.dataSource.getDataField(oSource, sFieldName);
									oRecord.setFieldValue(oDataField, vRecords[sId][sFieldName])
								}
							}
							oRecords.addRecord(oRecord);
						}
					}
				}
		}

		oJSONTypeHandler.unserializeAttributes(oAction, hAttributes);
		var oRequest = new btl.dataBinding.Request(oSource, oAction, bIsObserver ? oController : null);
		oRequest.send();
	}

	// If oObserver is a dataObserver control it will be refreshed
	// (except when it is a 'delete', 'update' or 'create' action)
	/**
	 * Applies an action, which is typically called upon server operation approval.
	 * It can also be called manually without waiting for a server response.
	 * @param {Object} source Reference to the dataSource controller object.
	 * @param {String} action Action performed.
	 * @param {Object} parameters Optional parameters object.
	 * @param {Element} observer Optional observer element.
	 * @return {Void} Nothing
	 * @publish
	 */
	btl.dataSource.actionApply = function btl_dataSource_actionApply(oSource, sAction, vParameters, oObserver) {
		if (!oSource || !oSource._)
			return;	//destroyed
		var oIdField = btl.dataSource.getDataField(oSource, 'identifier');
		switch (sAction) {
			case 'delete':
				if (vParameters instanceof Array) { // only array
					// delete removed records and their attributes
					for (var i = 0, iMax = vParameters.length; i < iMax; i++)
						if (oSource._.oRecords.hasRecord(vParameters[i])) {
							oSource._.oRecords.removeRecord(vParameters[i]);
							// XXX: Let removeRecord take care of updating the observers?
							var aObservers = oSource.getProperty('observers');
							for (var j = 0, iLimit = aObservers.length; iLimit > j; j++)
								if (vParameters[i] in aObservers[j]._.oIndexes)
									aObservers[j]._.oIndexes[vParameters[i]] = false;
						}
					// recreate dataSource array of existing indexes
					oSource._['_records'] = oSource._.oRecords.getRecordIds();
				}
				break;
			case 'create':
				if (vParameters instanceof Array) {//list of confirmed records
					//do nothing just confirm
				} else if (vParameters instanceof Object) { //actual data
					for (var sRecId in vParameters)
						if (vParameters.hasOwnProperty(sRecId)) {
							oSource._.oRecords.addRecord(new btl.dataBinding.Record(vParameters[sRecId], oSource.typeHandler, sRecId, oIdField));
						}
				}
				break;
			case 'update':
				if (vParameters instanceof Object) { // either array or object
					var aRecordIds = [];
					var hFields = {};

					if (vParameters instanceof Array) {//list of confirmed records
						for (var index in vParameters) {
							var sId = vParameters[index];
							if (vParameters.hasOwnProperty(index) && oSource._.oRecords.hasRecord(sId)) {
								var oRecord = oSource._.oRecords.getRecord(sId);
								oRecord.applyUpdates(hFields);
								aRecordIds[aRecordIds.length] = sId;
							}
						}
					} else {//records with new information
						for (var index in vParameters) {
							if (vParameters.hasOwnProperty(index)) {
								var oRecord = new btl.dataBinding.Record(vParameters[index], oSource.typeHandler, index, oIdField);
								oSource._.oRecords.addRecord(oRecord);
								oRecord.clearUpdates();
								aRecordIds[aRecordIds.length] = index;
							}
						}
					}
					if (aRecordIds.length) {
						var aObservers = oSource.getProperty('observers');
						for (var i = 0, iMax = aObservers.length; i < iMax; i++) {
							var sSortField = btl.dataSource.getDataField(oSource,
									aObservers[i].getAttribute('sortField')).name;
							for (var j = 0, jMax = aRecordIds.length; j < jMax; j++) {
								if (aRecordIds[j] in aObservers[i]._.oIndexes // record in a set
										&& sSortField in hFields) { // changed field is sort parameter
									aObservers[i].setProperty('sorted', false);
									break;
								}
							}
						}
					}
				}
				break;
			case 'select': // select some records with a condition
			case 'read':
			case 'sort':
				break;

		}//switch

		if (oObserver && oObserver._) {
				var oEvent = bb.document.createEvent('Events');
				oEvent.initEvent('actionResponse', false, false);
				oEvent.action = sAction;
				oEvent.data = vParameters;
					oObserver.dispatchEvent(oEvent);
		}
	}

	/**
	 * Registers a new data type or extends an existing one.
	 * @param {String} type Data type name (a MIME type).
	 * @param {Object} typeHandler Object implementing TypeHandler interface. Required methods are 'serialize', 'unserialize', 'getValue' and 'setValue'.
	 * @return {Object} On success, the TypeHandler object that was added; otherwise null.
	 * @publish
	 */
	btl.dataSource.addType = function btl_dataSource_addType(sType, oTypeHandler) {
		// XXX: for backwards compatibility
		return btl.dataSource.types.addType(sType, oTypeHandler);
	}

	/**
	 * Removes the data type.
	 * @param {String} type Data type name (a MIME type).
	 * @return {Object} Removed data type.
	 * @publish
	 */
	btl.dataSource.removeType = function btl_dataSource_removeType(sType) {
		// XXX: for backwards compatibility
		return btl.dataSource.types.removeType(sType);
	}


	/**
	 * This object keeps the types support, which are assigned by MIME types.
	 * @publish
	 */
	btl.dataBinding.TypeHandlerList = function() {
		this.types = {};

		var oXMLHandler = new btl.dataBinding.XMLTypeHandler();
		var oJSONHandler = new btl.dataBinding.JSONTypeHandler();

		this.addType('application/xml', oXMLHandler);
		this.addType('text/xml', oXMLHandler);
		this.addType('application/json', oJSONHandler);
		this.addType('text/json', oJSONHandler);
	}
	btl.dataBinding.TypeHandlerList.prototype = {};

	/**
	 * Registers a new data type or overwrites an existing one.
	 * @param {String} type Data type name (a MIME type).
	 * @param {Object} typeHandler Object implementing TypeHandler interface. Required methods are 'serialize', 'unserialize', 'getValue' and 'setValue'.
	 * @return {Object} On success, the TypeHandler object that was added; otherwise null.
	 */
	btl.dataBinding.TypeHandlerList.prototype.addType = function(sType, oTypeHandler) {
		if (btl.dataSource.testInterface(oTypeHandler, btl.dataBinding.TypeHandler)) {
			this.types[sType] = oTypeHandler;
			return oTypeHandler;
		} else
			bb.command.trace(null, "Can't add " + sType + " support: missing mandatory methods from TypeHandler interface.");
		return null;
	}

	btl.dataBinding.TypeHandlerList.prototype.getType = function(sType) {
		return this.types[sType];
	}

	/**
	 * Removes the data type.
	 * @param {String} type Data type name (a MIME type).
	 * @return {Object} Removed data type.
	 */
	btl.dataBinding.TypeHandlerList.prototype.removeType = function(sType) {
		if (this.types.hasOwnProperty(sType)) {
			var oTypeHandler = this.types[sType];
			delete this.types[sType];
			return oTypeHandler;
		}
		return null;
	}

	/**
	 * Tests whether an object implements an interface correctly.
	 * @return
	 * @publish
	 */
	btl.dataSource.testInterface = function(oObject, oInterface) {
		var oPrototype = oInterface.prototype;
		for (var i in oPrototype)
			if (oPrototype.hasOwnProperty(i) && (!(i in oObject) || oObject[i] == oPrototype[i]))
				return false;
		return true;
	}


	/**
	 * Interface for type objects
	 * @publish
	 */
	btl.dataBinding.TypeHandler = function() {
	}
	btl.dataBinding.TypeHandler.prototype = {};
	btl.dataBinding.TypeHandler.prototype.serialize = function(vData) {
		// implement
	}
	/**
	 *
	 * @param vData
	 * @param oIdField
	 * @param oDataSource
	 * @return
	 * @type btl.dataBinding.ActionGroup
	 */
	btl.dataBinding.TypeHandler.prototype.unserialize = function(vData, oIdField, oDataSource) {
		// implement
	}
	btl.dataBinding.TypeHandler.prototype.getValue = function(oRecord, oQuery, bRaw) {
		// implement
	}
	btl.dataBinding.TypeHandler.prototype.setValue = function(oRecord, oQuery, sValue) {
		// implement
	}


	/**
	 * XML type handler (application/xml)
	 * @publish
	 */
	btl.dataBinding.XMLTypeHandler = function() {
	}
	btl.dataBinding.XMLTypeHandler.prototype = {};

	btl.dataBinding.XMLTypeHandler.prototype.serialize = function(oActionGroup) {
//		if (typeof(oActionGroup) != 'object')
//			return ''; // XXX: fail silently?

		var oDocument = bb.xml.parse('<?xml version="1.0"?><request _actions_="true"/>');
		var oRootNode = oDocument.documentElement;

		var aActions = oActionGroup.getActionList();
		for (var i = 0; i < aActions.length; i++) {
			var oNode = this.serializeAction(aActions[i], oDocument);
			oRootNode.appendChild(oNode);
		}

		return bb.xml.serialize(oDocument);
	}

	btl.dataBinding.XMLTypeHandler.prototype.serializeAction = function(oAction, oDocument) {
		var oNode = oDocument.createElement(oAction.type);
		var hAttributes = oAction.getAttributeMap();
		for (var i in hAttributes)
			if (hAttributes.hasOwnProperty(i))
				oNode.setAttribute(i, String(hAttributes[i]));
		this.serializeRecordList(oAction.records, oNode, oDocument);
		return oNode;
	}

	btl.dataBinding.XMLTypeHandler.prototype.serializeRecordList = function(oRecordList, oNode, oDocument) {
		var oRecordsNode = oDocument.createElement('records');
		var aRecords = oRecordList.getRecordList();
		for (var i = 0, len = aRecords.length; i < len; i++)
			this.serializeRecord(aRecords[i], oRecordsNode, oDocument);
		if (oRecordsNode.firstChild)
			oNode.appendChild(oRecordsNode);
		return oNode;
	}

	btl.dataBinding.XMLTypeHandler.prototype.serializeRecord = function(oRecord, oNode, oDocument) {
		var oRecordNode = oDocument.createElement('record');
		var aFields = oRecord.getUpdatedFieldsList();
		var bIdentifier = false;
		for (var i = 0, len = aFields.length; i < len; i++) {
			this.serializeField(aFields[i], oRecordNode, oDocument);
			if (aFields[i].dataField.name == 'identifier')
				bIdentifier = true;
		}
		// make sure to add the identifier field as well
		if (!bIdentifier && oRecord.identifierField && oRecord.identifierField.dataField.select)
			this.serializeField(oRecord.identifierField, oRecordNode, oDocument);
		oNode.appendChild(oRecordNode);
		return oRecordNode;
	}

	btl.dataBinding.XMLTypeHandler.prototype.serializeField = function(oField, oNode, oDocument) {
		var sSelect = oField.dataField.select;
		var sValue = String(oField.value);
		if (sSelect.charAt(0) == '@') { // XXX: I don't think this XPath parsing is a good thing
			oNode.setAttribute(sSelect.substring(1), sValue);
		} else {
			var oFieldNode = oDocument.createElement(sSelect);
			var oTextNode = oDocument.createTextNode(sValue);
			oFieldNode.appendChild(oTextNode);
			oNode.appendChild(oFieldNode);
		}
	}

	btl.dataBinding.XMLTypeHandler.prototype.unserialize = function(vData, oIdField, oDataSource) {
		//sData - string, xml node, array of nodes (they must have the same ownerDocument)
		var oXml = typeof vData == 'string' ? bb.xml.parse(vData) : vData;
		var oActionGroup = new btl.dataBinding.ActionGroup();

		if (!oXml) // error in the response
			return oActionGroup;

		var oNode = oXml.nodeType == 9 ? oXml.documentElement : oXml;
		if ('length' in oXml) { // array or interface NodeList
			if (!oXml.length) {
				// TODO: move this if into unserializeAction...
				oActionGroup.addAction(new btl.dataBinding.Action('read', new btl.dataBinding.RecordList()));
			} else {
				oActionGroup.addAction(this.unserializeAction('read', oXml, oIdField));
			}
		} else {
			if (btl.isTrueValue("_actions_", oNode.getAttribute("_actions_"))) { // response with actions
				var aActions = bb.xml.selectNodes(oNode, '*');
				if (aActions.length) {
					for (var i = 0, iLimit = aActions.length; i < iLimit; i++) {
						if (aActions[i].nodeType == 1) {
							sAction = aActions[i].localName || aActions[i].tagName; // XXX: do not ignore namespace!
							oActionGroup.addAction(this.unserializeAction(sAction, aActions[i], oIdField));
						}
					}
				} else {
					var oErrorAction = this.unserializeAction('error', oNode, oIdField);
					if (oErrorAction && oErrorAction.hasAttribute('error'))
						oActionGroup.addAction(oErrorAction);
				}
			} else { //simple list of records
				var aNodes;
				if (oDataSource.hasAttribute('recordSelect'))
					aNodes = bb.xml.selectNodes(oNode, oDataSource.getAttribute('recordSelect'), oDataSource.modelNode);
				oActionGroup.addAction(this.unserializeAction('read', aNodes || oNode, oIdField));
			}
		}
		return oActionGroup;
	}

	btl.dataBinding.XMLTypeHandler.prototype.unserializeAction = function(sAction, vNode, oIdField) {
		// Get the records and put them in the data object
		var aNodes = 'length' in vNode ? vNode : bb.xml.selectNodes(vNode, '*');

		var oRecords = this.unserializeRecordList(aNodes, oIdField);
		var oAction = new btl.dataBinding.Action(sAction, oRecords)

		// XXX: shouldn't call unserializeAttributes with a node list...
		var oAttributes = this.unserializeAttributes(oAction, vNode);

		/*
		if (oAttributes.hasAttribute('records') && !aNodes.length) {
			// got list of records as attribute
			oRecords = new btl.dataBinding.RecordList();
			var aRecords = oAttributes.removeAttribute('records');
			for (var i = 0, len = aRecords.length; i < len; i++) {
				oRecords.addRecord(new btl.dataBinding.Record(null, null, aRecords[i], oIdField))
			}
		}
		*/
		return oAction;
	}

	btl.dataBinding.XMLTypeHandler.prototype.unserializeRecordList = function(aNodes, oIdField) {
		var oRecords = new btl.dataBinding.RecordList();
		if (oIdField.select) {
			// if the records have an ID
			for (var i = 0, iLimit = aNodes.length; i < iLimit; i++) {
				var oRecord = new btl.dataBinding.Record(aNodes[i], this, this.getValue(aNodes[i], oIdField), oIdField);
				oRecords.addRecord(oRecord);
			}
		} else {
			// if the records do not have an ID
			for (var i = 0, iLimit = aNodes.length; i < iLimit; i++) {
				var oRecord = new btl.dataBinding.Record(aNodes[i], this, i, oIdField);
				oRecords.addRecord(oRecord);
			}
		}
		return oRecords;
	}

	btl.dataBinding.XMLTypeHandler.prototype.unserializeAttributes = function(oAction, oNode) {
		var aAttributes = oNode.attributes;

		// Add attributes to attribute list
		for (var i = 0; aAttributes && i < aAttributes.length; i++) {
			var oAttribute = aAttributes.item(i),
				sName = oAttribute.nodeName,
				sValue = oAttribute.nodeValue;
			oAction.setAttributeString(sName, sValue);
		}
		return oAction;
	}

	btl.dataBinding.XMLTypeHandler.prototype.getValue = function(oRecord, oQuery, bRaw) {
		if (oRecord) {
			var oRes = bb.xml.selectSingleNode(oRecord, oQuery.select, oQuery.NSResolver);
			if (bRaw)
				return oRes;
			if (oRes && oRes.nodeType)
				// XXX: Why this serialize/replace?
				return bb.xml.serialize(oRes, true).replace(/<!\[CDATA\[(.*?)\]\]>/gm, '$1');
			else
				return oRes || '';
		}
		return bRaw ? oRecord : '';
	}

	btl.dataBinding.XMLTypeHandler.prototype.setValue = function(oRecord, oQuery, vValue) {
		if (oRecord) {
			var oRes = bb.xml.selectSingleNode(oRecord, oQuery.select, oQuery.NSResolver);
			if (oRes) {
				if (oRes.nodeType == 1) {
					if (oRes.firstChild && oRes.firstChild.nodeType == 3) {
						oRes.firstChild.nodeValue = vValue;
					} else {
						oRes.appendChild(oRecord.ownerDocument.createTextNode(vValue));
					}
				} else if (oRes.nodeType == 2) {
					oRes.nodeValue = vValue;
				}
			} else {
				try {
					// try to create the node from the XPath expression...
					if (oQuery.select.indexOf('@') == 0) {
						oRecord.setAttribute(oQuery.select.substr(1, oQuery.select.length), vValue);
					} else {
						// TODO: Add namespace support
						var oNew = oRecord.ownerDocument.createElement(oQuery.select);
						oNew.appendChild(oRecord.ownerDocument.createTextNode(vValue));
						oRecord.appendChild(oNew);
					}
				} catch (oError) {
					bb.command.trace(null, 'Error in xml.setValue: ' + oError, 3);
					return null;
				}
			}
			return vValue;
		}
		return null;
	}

	btl.dataBinding.XMLTypeHandler.prototype.getRecords = function(oRecord, oQuery) {
		var aRecords = [];
		if (oRecord) {
			var oRecordsList = bb.xml.selectNodes(oRecord, oQuery.select, oQuery.NSResolver || null);
			for (var i = 0; i < oRecordsList.length; i++)
				aRecords[aRecords.length] = oRecordsList[i];
		}
		return aRecords;
	}

	btl.dataBinding.XMLTypeHandler.prototype.toString = function() {
		return 'XMLTypeHandler';
	}

	/**
	 * JSON type handler (application/json)
	 * @publish
	 */
	btl.dataBinding.JSONTypeHandler = function() {
	}
	btl.dataBinding.JSONTypeHandler.prototype = {};

	btl.dataBinding.JSONTypeHandler.prototype.serialize = function(oActionGroup) {
		var hRoot = {};

		var aActions = oActionGroup.getActionList();
		for (var i = 0; i < aActions.length; i++)
			hRoot[aActions[i].type] = this.serializeAction(aActions[i]);

		return JSONRequest.serialize(hRoot);
	}

	btl.dataBinding.JSONTypeHandler.prototype.serializeAction = function(oAction) {
		var oJSONAction = {
				attributes: {},
				records: oAction.hasAttribute('records') ? oAction.getAttribute('records') :
						this.serializeRecordList(oAction.records)
		};
		var hAttributes = oAction.getAttributeMap();
		for (var i in hAttributes)
			if (hAttributes.hasOwnProperty(i) && i != 'records')
				oJSONAction.attributes[i] = hAttributes[i];
		return oJSONAction;
	}

	btl.dataBinding.JSONTypeHandler.prototype.serializeRecordList = function(oRecordList) {
		var aRecords = oRecordList.getRecordList();
		if (oRecordList.serializeAsArray) {
			var hRecords = [];
			for (var i = 0, len = aRecords.length; i < len; i++)
				hRecords[hRecords.length] = this.serializeRecord(aRecords[i], true);
		} else {
			var hRecords = {};
			for (var i = 0, len = aRecords.length; i < len; i++)
				hRecords[aRecords[i].identifier] = this.serializeRecord(aRecords[i], false);
		}
		return hRecords;
	}

	btl.dataBinding.JSONTypeHandler.prototype.serializeRecord = function(oRecord, bAddIdentifier) {
		var hRecord = {};
		var aFields = oRecord.getUpdatedFieldsList();
		var bIdentifier = false;
		for (var i = 0, len = aFields.length; i < len; i++) {
			hRecord[aFields[i].dataField.select] = this.serializeField(aFields[i]);
			if (aFields[i].dataField.name == 'identifier')
				bIdentifier = true;
		}
		// make sure to add the identifier field as well
		if (bAddIdentifier && !bIdentifier && oRecord.identifierField && oRecord.identifierField.dataField.select)
			hRecord[oRecord.identifierField.dataField.select] = this.serializeField(oRecord.identifierField);
		return hRecord;
	}

	btl.dataBinding.JSONTypeHandler.prototype.serializeField = function(oField) {
		return oField.value;
	}

	btl.dataBinding.JSONTypeHandler.prototype.unserialize = function(vData, oIdField, oDataSource) {
		var oData = typeof vData == 'string' ? JSONRequest.parse(vData) || [] : vData;

		var oActionGroup = new btl.dataBinding.ActionGroup();

		if (oData instanceof Array) {
			// only set of records
			var oAction = this.unserializeAction('read', oData, oIdField);
			oActionGroup.addAction(oAction);
		} else if (oData.hasOwnProperty('_actions_')) {
			delete oData['_actions_'];
			for (var sAction in oData) {
				if (oData.hasOwnProperty(sAction)) {
					var oAction = this.unserializeAction(sAction, oData[sAction], oIdField);
					oActionGroup.addAction(oAction);
				}
			}
		} else {
			// no actions, assume only records are there
			var oAction = this.unserializeAction('read', oData, oIdField);
			oActionGroup.addAction(oAction);
		}
		return oActionGroup;
	}

	btl.dataBinding.JSONTypeHandler.prototype.unserializeAction = function(sAction, oObject, oIdField) {
		var oAction = new btl.dataBinding.Action(sAction);
		var oRecordList = null;
		if (oObject instanceof Array) { // only set of records
			oRecordList = this.unserializeRecordList(oObject, oIdField);
		} else if (oObject.hasOwnProperty('error')) {
			oAction.setAttribute('error', oObject.error);
		} else {
			if (oObject.hasOwnProperty('records') && !(oObject.records instanceof Array && (typeof oObject.records[0] != 'object' || oObject.records[0] instanceof String))) {
				oRecordList = this.unserializeRecordList(oObject.records, oIdField);
				delete oObject.records;
			}
			if (oObject.hasOwnProperty('attributes')) {
				this.unserializeAttributes(oAction, oObject.attributes);
			} else {
				this.unserializeAttributes(oAction, oObject);
			}
		}
		oAction.setRecords(oRecordList || new btl.dataBinding.RecordList());
		return oAction;
	}

	btl.dataBinding.JSONTypeHandler.prototype.unserializeRecordList = function(oObject, oIdField) {
		var oRecordList = new btl.dataBinding.RecordList();

		for (var i in oObject) {
			if (oObject.hasOwnProperty(i)) {
				var oRecord = new btl.dataBinding.Record(oObject[i], this, i, oIdField);
				oRecordList.addRecord(oRecord);
			}
		}
		return oRecordList;
	}

	btl.dataBinding.JSONTypeHandler.prototype.unserializeAttributes = function(oAction, oObject) {
		for (var i in oObject)
			if (oObject.hasOwnProperty(i))
				oAction.setAttribute(i, oObject[i]);
		return oAction;
	}

	btl.dataBinding.JSONTypeHandler.prototype.getValue = function(oRecord, oQuery, bRaw) {
		// if oRecord is an object, return oQuery result, otherwise oRecord itself, otherwise ""
		var sQuery = oQuery.select;
		if (oRecord instanceof Object && sQuery) {
			if (sQuery.indexOf('.') != -1) {
				var aQuery = sQuery.split('.');
				for (var i = 0; i < aQuery.length && oRecord; i++)
					oRecord = oRecord[aQuery[i]];
			} else
				oRecord = oRecord[sQuery];
		}
		return oRecord != null ? oRecord : '';
	}

	btl.dataBinding.JSONTypeHandler.prototype.setValue = function(oRecord, oQuery, sValue) {
		if (!oRecord)
			return null;
		return oRecord[oQuery.select] = sValue;
	}

	btl.dataBinding.JSONTypeHandler.prototype.getRecords = function(oRecord, oQuery) {
		if (!oRecord)
			return [];
		var oRes = oRecord[oQuery.select];
		return oRes instanceof Object ? oRes : [];
	}

	btl.dataBinding.JSONTypeHandler.prototype.toString = function() {
		return 'JSONTypeHandler';
	}



	/**
	 * <p>The btl.dataSource.types object.</p>
	 * <p>This object keeps the types support, which are assigned by MIME types.</p>
	 */
	btl.dataSource.types = new btl.dataBinding.TypeHandlerList();


	/**
	 * A list of Formatters
	 * @publish
	 */
	btl.dataBinding.FormatterList = function() {
		this.formatters = {};
		this.parseRegexp = /\s*([^\[]+)\s*(?:\[(.*)\])?/;

		this.addFormatter('innerXml',   new btl.dataBinding.InnerXMLFormatter());
		this.addFormatter('raw',        new btl.dataBinding.RawFormatter());
		this.addFormatter('currency',   new btl.dataBinding.CurrencyFormatter());
		this.addFormatter('style',      new btl.dataBinding.StyleFormatter());
		this.addFormatter('htmlescape', new btl.dataBinding.HTMLEscapeFormatter());
	}
	btl.dataBinding.FormatterList.prototype = {};

	/**
	 * Registers the formatter. If there was an existing one for the specified type, it will be replaced.
	 * @param {String} name Formatter name.
	 * @param {Function} formatter Formatter function. Parameters are formattedValue, value (optional), parameters (optional).
	 * @param {Boolean} raw Optional flag indicating whether the data to be passed should be raw.
	 * @return {Function} Previously registered formatter.
	 * @example btl:btl_dataSource_addFormatter
	 * @publish
	 */
	btl.dataBinding.FormatterList.prototype.addFormatter = function(sName, oFormatter) {
		var oOldFormatter = this.getFormatter(sName);
		this.formatters[sName] = oFormatter;
		return oOldFormatter;
	}

	btl.dataBinding.FormatterList.prototype.getFormatter = function(sName) {
		return this.formatters[sName] || null;
	}

	/**
	 * Parses a formatter string that contains a formatter name and parameters
	 * @param sFormatterString The string to parse
	 * @return An object with two String fields, 'name' and 'parameters'.
	 * @type Object
	 */
	btl.dataBinding.FormatterList.prototype.parseFormatterString = function(sFormatterString) {
		var aMatches = this.parseRegexp.exec(sFormatterString);
		return { name: aMatches[1], parameters: aMatches[2] };
	}

	/**
	 * Unregisters the formatter and returns it.
	 * @param {String} name Formatter name.
	 * @return {Function} Unregistered formatter.
	 * @publish
	 */
	btl.dataBinding.FormatterList.prototype.removeFormatter = function(sName) {
		var oOldFormatter = this.getFormatter(sName);
		delete this.formatters[sName];
		return oOldFormatter;
	}


	/**
	 * Base class for formatters.
	 * @param bRaw
	 * @publish
	 */
	btl.dataBinding.Formatter = function() {
	}
	btl.dataBinding.Formatter.prototype = {};

	btl.dataBinding.Formatter.prototype.raw = false;

	btl.dataBinding.Formatter.prototype.format = function(sFormattedValue, sValue, sParameters) {
		// implement
	}


	/**
	 * @publish
	 */
	btl.dataBinding.InnerXMLFormatter = function() {
		btl.dataBinding.Formatter.call(this);
	}
	btl.dataBinding.InnerXMLFormatter.prototype = new btl.dataBinding.Formatter();

	btl.dataBinding.InnerXMLFormatter.prototype.format = function(sFormattedValue, vValue) {
		// XXX: interface says second parameter is string!
		return vValue && vValue.nodeType ? bb.xml.serialize(vValue, true) : vValue;
	}


	/**
	 * @publish
	 */
	btl.dataBinding.RawFormatter = function() {
		btl.dataBinding.Formatter.call(this);
	}
	btl.dataBinding.RawFormatter.prototype = new btl.dataBinding.Formatter();

	btl.dataBinding.RawFormatter.prototype.format = function(sFormattedValue, vValue) {
		// XXX: interface says second parameter is string!
		return vValue;
	}


	/**
	 * @publish
	 */
	btl.dataBinding.CurrencyFormatter = function() {
		btl.dataBinding.Formatter.call(this);
	}
	btl.dataBinding.CurrencyFormatter.prototype = new btl.dataBinding.Formatter();

	btl.dataBinding.CurrencyFormatter.prototype.format = function(sFormattedValue, vValue, sParameters) {
		// XXX: interface says second parameter is string!
		if (!sFormattedValue || !sFormattedValue.length)
			return sFormattedValue;
		switch(sParameters) {
		case 'euro':
			return "&euro;" + sFormattedValue;
		case 'pound':
			return "&pound;" + sFormattedValue;
		case 'yen':
			return "&yen;" + sFormattedValue;
		case 'cent':
			return "&cent;" + sFormattedValue;
		default:
			return "$" + sFormattedValue;
		}
	}


	/**
	 * @publish
	 */
	btl.dataBinding.StyleFormatter = function() {
		btl.dataBinding.Formatter.call(this);
	}
	btl.dataBinding.StyleFormatter.prototype = new btl.dataBinding.Formatter();

	btl.dataBinding.StyleFormatter.prototype.format = function(sFormattedValue, vValue, sStyle) {
		// XXX: interface says second parameter is string!
		return "<span style='" + sStyle + "'>" + sFormattedValue + "</span>";
	}


	/**
	 * @publish
	 */
	btl.dataBinding.HTMLEscapeFormatter = function() {
		btl.dataBinding.Formatter.call(this);
	}
	btl.dataBinding.HTMLEscapeFormatter.prototype = new btl.dataBinding.Formatter();

	btl.dataBinding.HTMLEscapeFormatter.prototype.format = function(sFormattedValue, vValue, sStyle) {
		// XXX: interface says second parameter is string!
		return bb.string.escapeXml(sFormattedValue);
	}


	//data formatters
	btl.dataBinding.formatters = new btl.dataBinding.FormatterList();



	/**
	 * Registers the formatter. If there was an existing one for the specified type, it will be replaced.
	 * @param {String} name Formatter name.
	 * @param {Function} formatter Formatter function. Parameters are formattedValue, value (optional), parameters (optional).
	 * @param {Boolean} raw Optional flag indicating whether the data to be passed should be raw.
	 * @return {Function} Previously registered formatter.
	 * @example btl:btl_dataSource_addFormatter
	 * @publish
	 */
	btl.dataSource.addFormatter = function btl_dataSource_addFormatter(sName, fFormatter, bRaw) {
		// XXX: for backwards compatibility
		var oFormatter = new btl.dataBinding.Formatter();
		oFormatter.raw = bRaw;
		oFormatter.format = fFormatter;
		oOldFormatter = btl.dataBinding.formatters.addFormatter(sName, oFormatter);
		return oOldFormatter ? oOldFormatter.format : null;
	}

	/**
	 * Unregisters the formatter and returns it.
	 * @param {String} name Formatter name.
	 * @return {Function} Unregistered formatter.
	 * @publish
	 */
	btl.dataSource.removeFormatter = function btl_dataSource_removeFormatter(sName){
		// XXX: for backwards compatibility
		oOldFormatter = btl.dataBinding.formatters.removeFormatter(sName);
		return oOldFormatter ? oOldFormatter.format : null;
	}

	/**
	 * Generates a hash array with attribute names as keys and controller attribute values as values.
	 * @param {Element} observer Reference to the observer element.
	 * @param {Array} attributes Array of attribute names.
	 * @param {Boolean} translate When set to true, the names of attributes will be translated with the controller schema.
	 * @return {Object} The object that was retrieved from the specified attribute list.
	 * @publish
	 */
	btl.dataSource.getObjectFromAttributes = function btl_dataSource_getObjectFromAttributes(oController, aAttrList, bTranslate){
		// XXX: it translates attribute values by dataSchema from dataSource of the oController if bTranslate is true
		var obj = {};
		var oSource = bTranslate ? oController.getProperty('dataSource') : null;

		if (aAttrList instanceof Array && aAttrList.length)
			for (var i=0, iMax = aAttrList.length; iMax > i; i++){
				obj[aAttrList[i]] = btl.dataSource.getDataField(oSource,
						oController.getAttribute(aAttrList[i])).select;
			}
		return obj;
	}

	/**
	 * Splits a space, comma or semicolon-separated string into an array.
	 * @param {String} value The string to split. If this is not a string, it will be returned as-is.
	 * @return An array containing strings.
	 * @type Array
	 */
	btl.dataSource.split = function btl_dataSource_split(sValue) {
		if (typeof sValue == 'string') {
			var aReturn = [];
			var aValue = sValue.split(/\s|;|,/);
			for (var i = 0, len = aValue.length; i < len; i++) {
				var sItem = aValue[i].replace(/^\s*(.*)\s*$/, '$1');
				if (sItem.length)
					aReturn[aReturn.length] = sItem;
			}
			return aReturn;
		} else {
			return sValue;
		}
	};

 	/**
	 * Checks if the record with the specified id exists in the dataSource.
 	 * @param {Element} source Reference to the dataSource element.
	 * @param {String} id Record identifier.
	 * @type {Boolean}
	 * @publish
	 */
	btl.dataSource.recordExists = function btl_dataSource_recordExists(oSource, sId) {
		return oSource._.oRecords.hasRecord(sId);
	}

	/**
	 * While processing an action request response, returns whether an existing record was updated.
	 * @param {Element} source Reference to the dataSource element.
	 * @param {String} id Record identifier.
	 * @type {Boolean}
	 * @publish
	 */
	btl.dataSource.recordChanged = function btl_dataSource_recordChanged(oSource, sId) {
		return oSource._.oRecords.hasChangedRecord(sId);
	}


	/**
	 * A DataSchema.
	 * @publish
	 */
	btl.dataBinding.DataSchema = function() {
		this.dataFields = {};
	}
	btl.dataBinding.DataSchema.prototype = {};

	btl.dataBinding.DataSchema.prototype.toString = function() {
		return 'DataSchema';
	}

	btl.dataBinding.DataSchema.prototype.init = function(oSchema) {
		if (oSchema._.bInitialized)
			return this;
		var aChildren = oSchema.getProperty('childNodes');
		for (var i = 0, iMax = aChildren.length; iMax > i; i++)
			if (bb.instanceOf(aChildren[i], btl.namespaceURI, 'dataField'))
				this.setField(aChildren[i].createDataFieldObject());
		oSchema._.bInitialized = true;
		return this;
	}

	btl.dataBinding.DataSchema.prototype.getFieldsList = function() {
		var aFields = [];
		for (var i in this.dataFields)
			if (this.dataFields.hasOwnProperty(i))
				aFields[aFields.length] = this.dataFields[i];
		return aFields;
	}

	btl.dataBinding.DataSchema.prototype.getFieldsMap = function() {
		return this.dataFields;
	}

	btl.dataBinding.DataSchema.prototype.getField = function(sName) {
		return this.dataFields[sName] || null;
	}

	btl.dataBinding.DataSchema.prototype.setField = function(oField) {
		this.dataFields[oField.name] = oField;
	}

	btl.dataBinding.DataSchema.prototype.removeField = function(sName) {
		if (!this.dataFields.hasOwnProperty(sName))
			return null;
		var oField = this.dataFields[sName];
		delete this.dataFields[sName];
		return oField;
	}



	/**
	 * Constructs a DataField object representing a dataField.
	 * @param name The name of the field.
	 * @param select The selection mapping for the field (optional, default equals name value)
	 * @param NSResolver The namespace resolver for the selection (only applies to XML) (optional, default null)
	 * @param format The formatter for the field (optional, default null)
	 * @param type The datatype for the field (optional, default 'default')
	 * @param dataSchema The dataSchema name that the field is referencing (optional, default null)
	 *
	 * @class DataField object, representing a field in the data.
	 * An instance of the DataField object has the following public properties:
	 * name, select, NSResolver, format, type, dataSchema.
	 * These are copied from the constructor parameters.
	 * @publish
	 */
	btl.dataBinding.DataField = function(sName, sSelect, oNSResolver, vFormat, sType, sDataSchema) {
		this.name = sName;
		this.select = sSelect != null ? sSelect : sName;
		this.NSResolver = oNSResolver || null;
		this.format = vFormat || null;
		this.type = sType || 'default';
		this.dataSchema = sDataSchema || null;
	}
	btl.dataBinding.DataField.prototype = {};



	/**
	 * Return a DataField object from the dataSchema,
	 * or, if it does not exist, return a new DataField object.
	 * @param dataSource The dataSource on which the dataSchema is specified
	 * @param name The name of the field to find
	 * @param NSResolver The NSResolver to use for the name in case the the field can not be found (optional)
	 * @return A DataField object
	 * @type DataField
	 * @publish
	 */
	btl.dataSource.getDataField = function btl_dataSource_getDataField(oDataSource, sName, oNSResolver) {
		// TODO: it was decided to throw an error/warning instead of returning a new DataField
		return oDataSource && oDataSource._.dataSchema.getField(sName) || new btl.dataBinding.DataField(sName, sName, oNSResolver);
	}



	/**
	 * Constructs an updated record field object
	 * @param dataField A DataField object instance which indicates the data field
	 * @param value The new value for the field
	 *
	 * @class UpdatedField object, representing an updated field in a record.
	 * An instance of the DataField object has the following public properties:
	 * dataField, value. These are copied from the constructor parameters.
	 * @publish
	 */
	btl.dataBinding.Field = function(oDataField, vValue) {
		this.dataField = oDataField;
		this.value = vValue;
	}
	btl.dataBinding.Field.prototype = {};



	/**
	 * Constructs a record object
	 * @param sourceRecord The source record object (JSON/XML)
	 * @param typeHandler A TypeHandler object to process the source record
	 * @param identifier The identifier of the record
	 * @param identifierField The identifier field object
	 * @class Record object, representing a data bound record.
	 * An instance of the Record object has the following public properties:
	 * identifier, source, updated.
	 * @publish
	 */
	btl.dataBinding.Record = function(oSourceRecord, oTypeHandler, sIdentifier, oIdentifierField) {
		this.source = oSourceRecord || null;
		this.typeHandler = oTypeHandler || null;
		this.fields = {};
		this.updated = false;
		this.updatedFields = {};

		this.identifier = sIdentifier != null ? sIdentifier : null;

		if (sIdentifier != null && oIdentifierField) {
			// add identifier field
			this.identifierField = new btl.dataBinding.Field(oIdentifierField, sIdentifier);
			this.fields[oIdentifierField.name] = this.identifierField;
		} else {
			this.identifierField = null;
		}
	}
	btl.dataBinding.Record.prototype = {};

	btl.dataBinding.Record.prototype.setFieldValue = function(oDataField, sValue) {
		this.updated = true;
		this.updatedFields[oDataField.name] = new btl.dataBinding.Field(oDataField, sValue);
	}

	btl.dataBinding.Record.prototype.setSourceFieldValue = function(oDataField, sValue) {
		return this.typeHandler.setValue(this.source, oDataField, sValue);
	}

	btl.dataBinding.Record.prototype.getFieldValue = function(oDataField, bRaw) {
		var oField = this.getField(oDataField);
		return oField ? oField.value : this.getSourceFieldValue(oDataField, bRaw);
	}

	btl.dataBinding.Record.prototype.getSourceFieldValue = function(oDataField, bRaw) {
		var vValue = this.typeHandler.getValue(this.source, oDataField, bRaw);
		// cache the value in a Field object (if not raw)
//		if (!bRaw)
//			this.fields[oDataField.name] = new btl.dataBinding.Field(oDataField, vValue);
		return vValue;
	}

	/**
	 * Get a Field object by name
	 * @param oDataField
	 * @return
	 */
	btl.dataBinding.Record.prototype.getField = function(oDataField) {
		var sName = oDataField.name;
		return (this.updatedFields.hasOwnProperty(sName) && this.updatedFields[sName]) ||
				(this.fields.hasOwnProperty(sName) && this.fields[sName]) || null;
	}

	/**
	 * Gets a list of all fields.
	 * @return A list of all fields
	 * @type Array
	 */
	btl.dataBinding.Record.prototype.getFieldsList = function() {
		var aList = this.getUpdatedFieldsList();
		for (var i in this.fields)
			if (this.fields.hasOwnProperty(i) && !this.updatedFields.hasOwnProperty(i))
				aList[aList.length] = this.fields[i];
		return aList;
	}

	/**
	 * Gets a list of all updated fields.
	 * @return A list of all updated fields
	 * @type Array
	 */
	btl.dataBinding.Record.prototype.getUpdatedFieldsList = function() {
		var aList = [];
		for (var i in this.updatedFields)
			if (this.updatedFields.hasOwnProperty(i))
				aList[aList.length] = this.updatedFields[i];
		return aList;
	}

	/**
	 * Clears all UpdatedFields from a record object.
	 */
	btl.dataBinding.Record.prototype.clearUpdates = function() {
		this.updated = false;
		this.updatedFields = {};
	}

	/**
	 *
	 * @param hFields Pre-existing hash object to update with modified fields (optional).
	 * @return Hash object containing the updated fields as key values.
	 */
	btl.dataBinding.Record.prototype.applyUpdates = function(hFields) {
		hFields = hFields || {};
		for (var i in this.updatedFields) {
			if (this.updatedFields.hasOwnProperty(i)) {
				var oField = this.updatedFields[i];
				this.setSourceFieldValue(oField.dataField, oField.value);
//				this.fields[i] = oField;
				hFields[oField.dataField.name] = true;
			}
		}
		this.clearUpdates();
		return hFields;
	}

	/**
	 * Copies the UpdatedFields from a record object.
	 * @param record The source record
	 */
	btl.dataBinding.Record.prototype.copyUpdates = function(oRecord) {
		this.updated = oRecord.updated;
		this.updatedFields = oRecord.updatedFields;
	}

	btl.dataBinding.Record.prototype.toString = function() {
		return 'Record (id: ' + this.identifier + ', updated: ' + this.updated + ', type: ' + this.typeHandler + ')';
	}



	/**
	 * Constructs a RecordList object containing records.
	 * @class This class represents a list of data bound records.
	 * An instance of this object has the following public properties:
	 * records, size.
	 * @publish
	 */
	btl.dataBinding.RecordList = function() {
		this.records = {};
		this.size = 0;
		this.changedRecords = {};
		this.serializeAsArray = false;	// XXX: dirty way to retain back compat - see bug 11626 comment 3
	}
	btl.dataBinding.RecordList.prototype = {};

	/**
	 * Adds a record to the RecordList.
	 * When a record with the same identifier already exists, the updates are carried over, and the record is replaced.
	 * @param record The Record object to add.
	 */
	btl.dataBinding.RecordList.prototype.addRecord = function(oRecord) {
		if (this.records.hasOwnProperty(oRecord.identifier)) {
			oRecord.copyUpdates(this.records[oRecord.identifier]);
			this.changedRecords[oRecord.identifier] = true;
		} else
			this.size++;
		this.records[oRecord.identifier] = oRecord;
	}

	/**
	 * Clears the list of changed records.
	 */
	btl.dataBinding.RecordList.prototype.clearChangedRecords = function() {
		this.changedRecords = {};
	}

	/**
	 * Returns whether the identified record is in the list of changed records.
	 */
	btl.dataBinding.RecordList.prototype.hasChangedRecord = function(sId) {
		return this.changedRecords.hasOwnProperty(sId);
	}

	/**
	 * Gets a record from the RecordList.
	 * @param id The identifier of the record.
	 * @return The Record object.
	 * @type Record
	 */
	btl.dataBinding.RecordList.prototype.getRecord = function(sId) {
		return this.records[sId] || null;
	}

	/**
	 * Returns whether the RecordList has a certain record.
	 * @param id The identifier of the record.
	 * @return <code>true</code> if the RecordList contains the record.
	 * @type Boolean
	 */
	btl.dataBinding.RecordList.prototype.hasRecord = function(sId) {
		return this.records.hasOwnProperty(sId);
	}

	/**
	 * Removes a record from the RecordList.
	 * @param id The identifier of the record.
	 * @return The Record object that was removed.
	 * @type Record
	 */
	btl.dataBinding.RecordList.prototype.removeRecord = function(sId) {
		if (this.records.hasOwnProperty(sId)) {
			var oRecord = this.records[sId];
			delete this.records[sId];
			this.size--;
			return oRecord;
		} else
			return null;
	}

	btl.dataBinding.RecordList.prototype.getRecordList = function() {
		var aRecords = [];
		for (var i in this.records)
			if (this.records.hasOwnProperty(i))
				aRecords[aRecords.length] = this.records[i];
		return aRecords;
	}

	btl.dataBinding.RecordList.prototype.getRecordMap = function() {
		return this.records;
	}

	/**
	 * Gets a list of record identifiers that are in the RecordList.
	 * @return List of record identifiers.
	 * @type Array
	 */
	btl.dataBinding.RecordList.prototype.getRecordIds = function() {
		var aList = [];
		for (var sId in this.records)
			if (this.records.hasOwnProperty(sId))
				aList[aList.length] = sId;
		return aList;
	}

	/**
	 * Gets a list of record identifiers that are in the RecordList and have the updated flag.
	 * @return List of record identifiers.
	 * @type Array
	 */
	btl.dataBinding.RecordList.prototype.getUpdatedRecordIds = function() {
		var aList = [];
		for (var sId in this.records)
			if (this.records.hasOwnProperty(sId) && this.records[sId].updated)
				aList[aList.length] = sId;
		return aList;
	}

	btl.dataBinding.RecordList.prototype.toString = function() {
		return 'RecordList (size: ' + this.size + ')';
	}



	/**
	 * Constructs a RecordCache object containing records, and a cache pruning mechanism.
	 * @class This class represents a list of data bound records, and a cache pruning mechanism.
	 * An instance of this object has the following public properties:
	 * records, size, cacheLimit.
	 * @publish
	 */
	btl.dataBinding.RecordCache = function() {
		btl.dataBinding.RecordList.call(this);
		this.cacheLimit = Infinity;
	}
	btl.dataBinding.RecordCache.prototype = new btl.dataBinding.RecordList();

	/**
	 * Sets the cache limit of the RecordList
	 * @param {Number} cacheLimit The new value for the cache limit
	 */
	btl.dataBinding.RecordCache.prototype.setCacheLimit = function(iCacheLimit) {
		this.cacheLimit = iCacheLimit;
		// XXX: call prune here?
	}

	/**
	 * Prunes the RecordList. All records that are not in the indexes list of any of the
	 * provided observers, nor have pending updates, will be removed from the RecordList.
	 * @param {Array} observers The observers that use this RecordList.
	 */
	btl.dataBinding.RecordCache.prototype.prune = function(aObservers) {
		if (this.cacheLimit < this.size) {
			// make list of records that are in use (can not be pruned)
			var hAllRecords = {};
			for (var i = 0, iLimit = aObservers.length; i < iLimit; i++) {
				var aIndexes = aObservers[i].getProperty('indexes');
				for (var j = 0, jLimit = aIndexes.length; j < jLimit; j++)
					hAllRecords[aIndexes[j]] = true;
			}
			// remove all unused records
			for (var sRecordId in this.records)
				if (this.records.hasOwnProperty(sRecordId) && !hAllRecords.hasOwnProperty(sRecordId) && !this.records[sRecordId].updated)
					this.removeRecord(sRecordId);
		}
	}

	btl.dataBinding.RecordCache.prototype.toString = function() {
		return 'RecordCache (size: ' + this.size + ', cache limit: ' + this.cacheLimit + ')';
	}



	/**
	 * A group of actions
	 * @publish
	 */
	btl.dataBinding.ActionGroup = function() {
		this.actions = {};
	}
	btl.dataBinding.ActionGroup.prototype = {};

	btl.dataBinding.ActionGroup.prototype.addAction = function(oAction) {
		this.actions[oAction.type] = oAction;
	}

	btl.dataBinding.ActionGroup.prototype.getAction = function(sAction) {
		return this.actions[sAction] || null;
	}

	btl.dataBinding.ActionGroup.prototype.getActionList = function() {
		var aActions = [];
		for (var i in this.actions)
			if (this.actions.hasOwnProperty(i))
				aActions.push(this.actions[i]);
		return aActions;
	}

	btl.dataBinding.ActionGroup.prototype.toString = function() {
		return 'ActionGroup';
	}


	/**
	 * An action.
	 * Note that every action must have a RecordList, so if you pass null to the constructor,
	 * you must ensure to set a RecordList before the object is used using the setRecords() method.
	 * Public properties:
	 * - records, containing a RecordList.
	 * @param sAction The action type (create/read/update/delete)
	 * @param oRecordList A list of records (optional).
	 * @publish
	 */
	btl.dataBinding.Action = function(sAction, oRecordList) {
		this.type = sAction;
		this.records = oRecordList || null;
		this.attributes = {};
	}
	btl.dataBinding.Action.prototype = {};

	btl.dataBinding.Action.prototype.toString = function() {
		return 'Action (' + this.type + ')';
	}

	/**
	 * Sets the Action's records to the provided RecordList instance.
	 * @param oRecordList A RecordList instance.
	 */
	btl.dataBinding.Action.prototype.setRecords = function(oRecordList) {
		return this.records = oRecordList;
	}

	btl.dataBinding.Action.prototype.setAttribute = function(sName, vValue) {
		return this.attributes[sName] = vValue;
	}

	btl.dataBinding.Action.prototype.getAttribute = function(sName) {
		return this.attributes[sName];
	}

	btl.dataBinding.Action.prototype.hasAttribute = function(sName) {
		return this.attributes.hasOwnProperty(sName);
	}

	btl.dataBinding.Action.prototype.removeAttribute = function(sName) {
		var vValue = this.attributes[sName];
		delete this.attributes[sName];
		return vValue;
	}

	/**
	 * Returns the hash object with attribute key/values
	 * @return
	 */
	btl.dataBinding.Action.prototype.getAttributeMap = function() {
		return this.attributes;
	}

	/**
	 * Overwrites the action attributes with the provided attribute hash object
	 * @param hAttributes A hash object containing attribute key/values in their required type.
	 */
	btl.dataBinding.Action.prototype.setAttributeMap = function(hAttributes) {
		return this.attributes = hAttributes;
	}

	/**
	 * Sets an attribute from a string value, which it casts to the desired type.
	 * @param {String} sName Attribute name
	 * @param {String} sValue Attribute value
	 * @return The attribute value set.
	 */
	btl.dataBinding.Action.prototype.setAttributeString = function(sName, sValue) {
		switch (sName) {
		case 'records':
		case 'indexes':
			sValue = btl.dataSource.split(sValue);
			break;
		case 'totalRecords':
		case 'rangeStart':
		case 'rangeEnd':
			sValue = parseInt(sValue, 10);
			break;
		}
		return this.attributes[sName] = sValue;
	}


	/**
	 * An object representing a request
	 * @param oAction A btl.dataBinding.Action instance to be serialized and transmitted with the request.
	 * @param oObserver The request observer (optional). When omitted, <em>all</em> observers registered to
	 *                  the dataSource will be notified.
	 */
	btl.dataBinding.Request = function(oDataSource, oAction, oObserver) {
		this.dataSource = oDataSource;
		this.action = oAction;
		this.observer = oObserver;
	}
	btl.dataBinding.Request.prototype = {};

	btl.dataBinding.Request.prototype.toString = function() {
		return 'Request';
	}

	btl.dataBinding.Request.prototype.send = function() {
		this.dataSource.sendRequest(this.action, this.observer);
	}



	btl.dataBinding.Response = function(oActionGroup, oRequest) {
		this.actionGroup = oActionGroup;
		this.request = oRequest
	}
	btl.dataBinding.Response.prototype = {};

	btl.dataBinding.Response.prototype.toString = function() {
		return 'Response';
	}

}]]></d:resource>

			<d:attribute name="name">
				
			</d:attribute>

			<d:attribute name="cacheLimit" default="Infinity">
				
				<d:mapper type="text/javascript"><![CDATA[
					if (this._.oRecords)
						this._.oRecords.setCacheLimit(this.getProperty('cacheLimit'));
				]]></d:mapper>
			</d:attribute>

			<d:property name="name">
				<d:getter type="text/javascript"><![CDATA[
					var sName = this.getAttribute('name');
					if (!sName) {
						// if no name specified, generate one
						var sName = btl.dataBinding.dataSources.getUniqueSourceName();
						this.setAttribute('name', sName);
					}
					return sName;
				]]></d:getter>
				<d:setter type="text/javascript"><![CDATA[
					this.setAttribute('name', value);
				]]></d:setter>
			</d:property>

			<d:property name="cacheLimit">
				<d:setter type="text/javascript"><![CDATA[
					this.setAttribute('cacheLimit', value);
				]]></d:setter>
				<d:getter type="text/javascript"><![CDATA[
					var iCacheLimit = this.hasAttribute('cacheLimit') ? Number(this.getAttribute('cacheLimit')) : Infinity;
					return !isNaN(iCacheLimit) ? iCacheLimit : Infinity;
				]]></d:getter>
			</d:property>

			<d:property name="totalRecords">
				
			</d:property>

			<d:property name="dataSchema">
				
				<d:getter type="text/javascript"><![CDATA[
					return this._.dataSchema;
				]]></d:getter>
			</d:property>

			<d:property name="observers">
				
				<d:getter type="text/javascript"><![CDATA[
					if (!this._._observers)
						this._._observers = [];
					return this._._observers;
				]]></d:getter>
				<d:setter type="text/javascript"><![CDATA[
					this._._observers = value;
				]]></d:setter>
			</d:property>

			<d:method name="updateObservers">
				
				<d:argument name="action">
					
				</d:argument>
				<d:argument name="records">
					
				</d:argument>
				<d:argument name="observer">
					
				</d:argument>
				<d:argument name="actionObj">
					
				</d:argument>
				<d:body type="text/javascript"><![CDATA[
					if (observer) {
						// update one observer (only if it observes this dataSource)
						if (observer.getProperty('dataSource') == this)
							observer.dataUpdate(action, records, actionObj);
					} else {
						// update all observers
						var aObservers = this.getProperty('observers');
						for (var i = 0, iLimit = aObservers.length; iLimit > i; i++)
							aObservers[i].dataUpdate(action, records, actionObj);
					}
				]]></d:body>
			</d:method>

			<d:method name="acceptData">
				
				<d:argument name="actionGroup">
					
				</d:argument>
				<d:argument name="observer">
					
				</d:argument>
				<d:body type="text/javascript"><![CDATA[
					btl.dataBinding.acceptData(this, actionGroup, observer);
				]]></d:body>
			</d:method>

			<d:method name="acceptRawData">
				
				<d:argument name="data">
					
				</d:argument>
				<d:argument name="observer">
					
				</d:argument>
				<d:body type="text/javascript"><![CDATA[
					var oActionGroup = this.typeHandler.unserialize(data, btl.dataSource.getDataField(this, 'identifier'), this);
					this.acceptData(oActionGroup, observer);
				]]></d:body>
			</d:method>

			<d:method name="addObserver">
				
				<d:argument name="observer">
					
				</d:argument>
				<d:body type="text/javascript"><![CDATA[
					var aObservers = this.getProperty('observers');
					if (bb.array.indexOf(aObservers, observer) == -1) {
						aObservers.push(observer);
						observer.setProperty('dataSource', this);
						if (this._.bInitialized) //already initialized
							observer.refresh();
					}
				]]></d:body>
			</d:method>

			<d:method name="removeObserver">
				
				<d:argument name="observer">
					
				</d:argument>
				<d:body type="text/javascript"><![CDATA[
					if (!this._)
						return;
					bb.array.removeObject(this.getProperty('observers'), observer);
					if (observer._) {
						observer.setProperty('dataSource', null);
						observer.clear();
					}
					return observer;
				]]></d:body>
			</d:method>

			<d:method name="refresh">
				
				<d:body type="text/javascript"><![CDATA[
					var aObservers = this.getProperty('observers');
					for (var i = 0, iLimit = aObservers.length; i < iLimit; i++)
						aObservers[i].refresh();
				]]></d:body>
			</d:method>

			<d:method name="actionRequest">
				
				<d:argument name="observer">
					
				</d:argument>
				<d:argument name="action">
					
				</d:argument>
				<d:argument name="parameters">
					
				</d:argument>
				<d:body type="text/javascript"><![CDATA[
					btl.dataSource.actionRequest( observer ? observer : this, action, parameters);
				]]></d:body>
			</d:method>

			<d:constructor type="text/javascript"><![CDATA[
				// initialise with dummy dataSchema
				this._.dataSchema = new btl.dataBinding.DataSchema();
				this._.dataSchema.setField(new btl.dataBinding.DataField('identifier', '', null));
				
				this._['_totalRecords'] = 0;
				this._['_records'] = [];
				this._.oRecords = new btl.dataBinding.RecordCache();
				this._.oRecords.setCacheLimit(this.getProperty('cacheLimit'));
				//Queing requests so that no out-of-sync errors happening
				this._._requestQueue = [];
			]]></d:constructor>

			<d:handler event="DOMNodeInsertedIntoDocument" type="text/javascript"><![CDATA[
				var aSchemas = [];
				var aChildren = this.getProperty('childNodes');
				for (var i = 0, iMax = aChildren.length; iMax > i; i++) {
					if (bb.instanceOf(aChildren[i], btl.namespaceURI, 'dataSchema')) {
						aSchemas[aSchemas.length] = aChildren[i];
						break; //TO-DO:remove for hierarchical data access
					}
				}
				if (aSchemas.length === 1) {
					this._.dataSchema = aSchemas[0].getProperty('dataSchema');
				} else {
					this._.aSchemas = aSchemas;
				}
				this._.bInitialized = true;
				btl.dataBinding.registerSource(this);
			]]></d:handler>

			<d:handler event="DOMNodeRemovedFromDocument" type="text/javascript"><![CDATA[
				this._.bRemoved = true;
				btl.dataBinding.removeSource(this);
			]]></d:handler>
		</d:element>

		<!-- ############################################# dataContainer ################################## -->
		<d:element name="dataContainer">
			

			<d:method name="__children">
				
				<d:body type="text/javascript"/>
			</d:method>
		</d:element>

		<!-- ############################################# dataSchema ################################## -->
		<d:element name="dataSchema" extends="b:element">
			

			<d:attribute name="identifier" default="">
				
			</d:attribute>

			<d:attribute name="xmlnsPrefix">
				
				<d:mapper type="text/javascript"><![CDATA[
					bb.command.trace(this, 'The xmlnsPrefix attribute has been removed. Please define the prefix using XML Namespaces.', 2);
				]]></d:mapper>
			</d:attribute>

			<d:attribute name="dataSelect">
				
			</d:attribute>

			<d:attribute name="recordSelect">
				
			</d:attribute>

			<d:property name="dataSchema">
				
				<d:getter type="text/javascript"><![CDATA[
					if (!this._.bInitialized)
						this._._dataSchema.init(this);
					return this._._dataSchema;
				]]></d:getter>
			</d:property>

			<d:constructor type="text/javascript"><![CDATA[
				var oDataSchema = new btl.dataBinding.DataSchema();
				oDataSchema.setField(new btl.dataBinding.DataField('identifier', ''));

				var aAttr = this.modelNode.attributes;
				if (aAttr) {
					var oKnownAttr = {};
					for (var i=0, iMax = aAttr.length; iMax > i; i++) {
						var sName = aAttr[i].nodeName;
						if (!oKnownAttr[sName] && sName != 'bid__') { //ignore internals
							oDataSchema.setField(new btl.dataBinding.DataField(
									sName,
									aAttr[i].nodeValue,
									this.modelNode
								));
						}
					}
				}
				this._._dataSchema = oDataSchema;
			]]></d:constructor>

			<d:handler event="DOMNodeInsertedIntoDocument" type="text/javascript"><![CDATA[
				this._._dataSchema.init(this);
			]]></d:handler>
		</d:element>

		<!-- ############################################# dataField ################################## -->
		<d:element name="dataField" extends="b:element">
			

			<d:attribute name="select">
				
			</d:attribute>

			<d:attribute name="name">
				
			</d:attribute>

			<d:attribute name="type">
				
			</d:attribute>

			<d:attribute name="format">
				
			</d:attribute>

			<d:attribute name="dataSchema">
				
			</d:attribute>
			
			<d:method name="createDataFieldObject">
				
				<d:body type="text/javascript"><![CDATA[
					var sName = this.getAttribute('name');
					var sSelect = this.hasAttribute('select') ? this.getAttribute('select') : sName;
					var aFormat = btl.dataSource.getFormat(null, null, this.getAttribute('format'));
	
					return new btl.dataBinding.DataField(
							sName,
							sSelect,
							this.modelNode,
							aFormat.length > 1 ? aFormat : aFormat.length ? aFormat[0] : null,
							this.getAttribute('type'),
							this.getAttribute('dataSchema')
						);
				]]></d:body>
			</d:method>

			<d:handler event="DOMNodeInsertedIntoDocument" type="text/javascript"><![CDATA[
				var sName = this.getAttribute('name');
				if (!sName || !sName.length) {
					bb.command.trace(this, "dataField 'name' attribute is not defined.");
				}
			]]></d:handler>
		</d:element>

		<d:element name="dataFormatter">
			

			<d:attribute name="name">
				
			</d:attribute>

			<d:attribute name="raw" default="false">
				
			</d:attribute>

			<d:attribute name="type" default="text/javascript">
				
			</d:attribute>

			<d:method name="__children">
				
				<d:body type="text/javascript"/>
			</d:method>

			<d:constructor type="text/javascript"><![CDATA[
				var sName = this.getAttribute("name");
				if (!sName || !sName.length){
					bb.command.trace(this, "No name is provided for data formatter", 3);
					return;
				}
				btl.dataSource.addFormatter(sName,
					new Function("formattedValue", "value", "parameters", this.getProperty('textContent')),
					this.getAttribute('raw') == 'true' ? true : false);
			]]></d:constructor>
		</d:element>

		<!-- ############################################# dataObserver ################################## -->
		<d:element name="dataObserver" extends="b:element" abstract="true">
			

			<d:resource type="text/javascript"><![CDATA[if (!btl.dataBinding) {
	/**
	 * <p>The btl.dataBinding API object.</p>
	 * <p>On this object you will find data binding shared API functions.</p>
	 * @publish
	 */
	btl.dataBinding = {};

	/**
	 * A list of DataSources
	 * @publish
	 */
	btl.dataBinding.DataSourceList = function() {
		this.sources = {};
		this.availableObservers = []; // observers that have not been attached to a source yet
		this.lastIndex = 0;
	}
	btl.dataBinding.DataSourceList.prototype = {};

	btl.dataBinding.DataSourceList.prototype.getSource = function(sName) {
		return this.sources[sName] || null;
	}

	btl.dataBinding.DataSourceList.prototype.getUniqueSourceName = function() {
		return 'BtlDataSource_' + this.lastIndex++;
	}

	btl.dataBinding.DataSourceList.prototype.registerSource = function(oDataSource) {
		var sName = oDataSource.getProperty('name');
		this.sources[sName] = oDataSource;

		// refresh all registered observers
		var aObservers = oDataSource.getProperty('observers');
		for (var i = 0, iMax = aObservers.length; i < iMax; i++)
			aObservers[i].refresh();

		// retry all unregistered observers
		for (var i = 0, len = this.availableObservers.length; i < len; i++)
			this.registerObserver(this.availableObservers.shift());
	}

	btl.dataBinding.DataSourceList.prototype.removeSource = function(oDataSource) {
		var sName = oDataSource.getProperty('name');
		if (sName in this.sources) {
			delete this.sources[sName];
			var aObservers = oDataSource.getProperty('observers').slice(0);
			// remove observers, and attempt to re-register them...
			for (var i = 0, iLimit = aObservers.length; iLimit > i; i++)
				this.registerObserver(oDataSource.removeObserver(aObservers[i]));
		}
	}

	btl.dataBinding.DataSourceList.prototype.addAvailableObserver = function(oObserver) {
		for (var i = 0, len = this.availableObservers.length; i < len; i++)
			if (this.availableObservers[i] == oObserver)
				return; // don't add if already in list
		this.availableObservers.push(oObserver);
	}

	btl.dataBinding.DataSourceList.prototype.removeAvailableObserver = function(oObserver) {
		for (var i = 0, len = this.availableObservers.length; i < len; i++)
			if (this.availableObservers[i] == oObserver)
				return this.availableObservers.splice(i, 1)[0];
	}

	btl.dataBinding.DataSourceList.prototype.registerObserver = function(oObserver) {
		var oDataSource = oObserver.getDataSource();

		if (oDataSource) {
			oDataSource.addObserver(oObserver);
			this.removeAvailableObserver(oObserver);
		} else {
			// datasource not created yet or not initialized
			this.addAvailableObserver(oObserver);
		}
	}

	btl.dataBinding.DataSourceList.prototype.unregisterObserver = function(oObserver) {
		var oSource = oObserver.getProperty('dataSource');
		if (oSource)
			oSource.removeObserver(oObserver);
		this.removeAvailableObserver(oObserver);
	}

	btl.dataBinding.dataSources = new btl.dataBinding.DataSourceList();


	btl.dataBinding.getSource = function btl_dataBinding_getSource(sName) {
		// XXX: for backwards compatibility
		return sName && sName.length ? btl.dataBinding.dataSources.getSource(sName) : null;
	}

	btl.dataBinding.registerSource = function btl_dataBinding_registerSource(oDataSource){
		btl.dataBinding.dataSources.registerSource(oDataSource);
	}

	btl.dataBinding.removeSource = function btl_dataBinding_removeSource(oDataSource) {
		btl.dataBinding.dataSources.removeSource(oDataSource);
	}

	btl.dataBinding.registerObserver = function btl_dataBinding_registerObserver(oObserver) {
		btl.dataBinding.dataSources.registerObserver(oObserver);
	}

	//get data
	btl.dataBinding.refreshObserver = function btl_dataBinding_refreshObserver(oObserver, bClear) {
		btl.dataSource.actionRequest(oObserver, 'read', {'attributes': oObserver.getProperty('state')}, bClear);
	}

	btl.dataBinding.acceptData = function btl_dataBinding_acceptData(oDataSource, oActionGroup, oObserver) {
		var oEvent = bb.document.createEvent('Events');
		oEvent.initEvent('dataUpdate', false, false);
		oEvent.data = oActionGroup;
		oEvent.dataObserver = oObserver;
		oDataSource.dispatchEvent(oEvent);

		var aActions = oActionGroup.getActionList();

		for (var i = 0; i < aActions.length; i++) {
			var oAction = aActions[i];
			var oRecords = oAction.records;

			// throw error event if there is an error attribute
			if (oAction.hasAttribute('error'))
				btl.dataBinding.fireErrorEvent(oDataSource, 0, oAction.getAttribute('error'));

			switch (oAction.type) {
				case 'error': // error message was already shown
					continue;
				case 'sort':
				case 'read':
					// build row indexes, use indexes attribute if available
					var aIndexes = oAction.getAttribute('indexes') || oRecords.getRecordIds();

					// add records to record cache
					// remove record IDs from aIndexes that are not known
					var aNewIndexes = [];
					for (var j = 0, iMax = aIndexes.length; j < iMax; j++) {
						var sIndex = aIndexes[j];
						if (oRecords.hasRecord(sIndex)) {
							oDataSource._.oRecords.addRecord(oRecords.getRecord(sIndex));
							aNewIndexes[aNewIndexes.length] = sIndex;
						} else if (oDataSource._.oRecords.hasRecord(sIndex)) {
							aNewIndexes[aNewIndexes.length] = sIndex;
						}
					}
					aIndexes = aNewIndexes;

					oDataSource._['_totalRecords'] = oAction.hasAttribute('totalRecords') ?
							oAction.getAttribute('totalRecords') : aIndexes.length;
					oDataSource._['_records'] = aIndexes;
					btl.dataSource.actionApply(oDataSource, oAction.type, aIndexes, oObserver);
					oDataSource.updateObservers(oAction.type, aIndexes, oObserver, oAction);
					break;
				case 'create':
				case 'delete':
				case 'update':
					// indexes whose updates are confirmed
					var aIndexes = oAction.getAttribute('records') || oRecords.getRecordIds() || [];

					if (oAction.hasAttribute('totalRecords')) {
						oDataSource._['_totalRecords'] = oAction.getAttribute('totalRecords');
					} else {
						if (oAction.type == 'create')
							oDataSource._['_totalRecords'] += aIndexes.length;
						else if (oAction.type == 'delete')
							oDataSource._['_totalRecords'] -= aIndexes.length;
					}
					btl.dataSource.actionApply(oDataSource, oAction.type, oRecords.size ? oRecords : aIndexes, oObserver);
					// delete, update and create are for all observers
					oDataSource.updateObservers(oAction.type, aIndexes, null, oAction);
					break;
				default:
					//it also fires actionResponse event
					btl.dataSource.actionApply(oDataSource, oAction.type, oAction, oObserver);
					var aIndexes = oAction.getAttribute('indexes') || null;
					oDataSource.updateObservers(oAction.type, aIndexes, oObserver, oAction);
			}
		}//for
		oDataSource._.oRecords.clearChangedRecords();
		oDataSource._.oRecords.prune(oDataSource.getProperty('observers'));
	}

	btl.dataBinding.fireErrorEvent = function btl_dataBinding_fireErrorEvent(oSource, errorCode, errorText) {
		var oEvent = bb.document.createEvent('Events');
		oEvent.initEvent('error', true, false);
		oEvent.message = errorText;
		oEvent.code = errorCode;
		oSource.dispatchEvent(oEvent);
		return oEvent;
	}

	btl.dataBinding.setType = function btl_dataBinding_setType(oDataSource, sType) {
		var oTypeHandler = btl.dataSource.types.getType(sType);
		if (!oTypeHandler) {
			bb.command.trace(oDataSource, 'Data type: ' + sType + ' is not supported.');
			return false;
		}
		oDataSource.dataType = sType;
		oDataSource.typeHandler = oTypeHandler;
		return true;
	}

	// Returns unformatted value directly from a record which possibly is not in a dataSource record pool
	btl.dataBinding.getRecordValue = function btl_dataSource_getRecordValue(oSource, oRecord, oDataField) {
		return oSource.typeHandler.getValue(oRecord, oDataField);
	}


	/**
	 * A list of Sorters.
	 * @publish
	 */
	btl.dataBinding.SorterList = function() {
		this.sorters = [];

		this.addSorter('default', new btl.dataBinding.DefaultSorter());
		this.addSorter('string',  new btl.dataBinding.StringSorter());
		this.addSorter('number',  new btl.dataBinding.NumberSorter());
		this.addSorter('date',    new btl.dataBinding.DateSorter());
	}
	btl.dataBinding.SorterList.prototype = {};

	btl.dataBinding.SorterList.prototype.addSorter = function(sName, oSorter) {
		this.sorters[sName] = oSorter;
		this[sName] = oSorter;	// XXX: for backwards compatibility
		return oSorter;
	}

	btl.dataBinding.SorterList.prototype.getSorter = function(sName) {
		return this.sorters[sName] || null;
	}

	btl.dataBinding.SorterList.prototype.removeSorter = function(sName) {
		var oSorter = this.getSorter(sName);
		if (oSorter) {
			delete this.sorters[sName];
			delete this[sName];
		}
		return oSorter;
	}

	/**
	 * Sorts an array of objects by their value property.
	 * @param {Array} aArray Array containing objects with value property to sort on.
	 * @param {String} sType Sorting type (optional).
	 * @param {Boolean} bDescend The sorting direction (optional).
	 * @return Sorted array.
	 */
	btl.dataBinding.SorterList.prototype.sortObjectArray = function(aArray, sType, bDescend) {
		var oSorter = sType && btl.dataBinding.sorters.getSorter(sType) ||
				btl.dataBinding.sorters.getSorter('default');

		return oSorter.sortObjectArray(aArray, bDescend);
	}



	/**
	 * An object implementing a sorting method
	 * @publish
	 */
	btl.dataBinding.Sorter = function() {
		this.sortDirection = 1; // 1 ascending, -1 descending
	}
	btl.dataBinding.Sorter.prototype = {};

	btl.dataBinding.Sorter.prototype.compare = function(a, b) {
		// implement
	}

	/**
	 * Sorts an array of objects by their value property.
	 * @param {Array} aArray Array containing objects with value property to sort on.
	 * @param {Boolean} bDescend The sorting direction (optional).
	 * @return Sorted array.
	 */
	btl.dataBinding.Sorter.prototype.sortObjectArray = function(aArray, bDescend) {
		var oThis = this;
		this.sortDirection = bDescend ? -1 : 1;

		aArray.sort(function(a, b) {
				return oThis.compare(a.value, b.value);
			});

		return aArray;
	}



	/**
	 * @publish
	 */
	btl.dataBinding.DefaultSorter = function() {
		btl.dataBinding.Sorter.call(this);
	}
	btl.dataBinding.DefaultSorter.prototype = new btl.dataBinding.Sorter();

	btl.dataBinding.DefaultSorter.prototype.compareNumberRegExp = /^[\.0-9]+$/;

	btl.dataBinding.DefaultSorter.prototype.compare = function(a, b) {
		var oValue1 = a;
		var oValue2 = b;
		var iType = 0;

		if (this.compareNumberRegExp.test(oValue1)) {
			oValue1 = parseFloat(oValue1);
			iType = 1;
		}
		if (this.compareNumberRegExp.test(oValue2)) {
			oValue2 = parseFloat(oValue2);
			iType += 2;
		}

		// Always put numbers in front of alphabetic characters (or reversed)
		if (iType == 3)
			return (oValue1 - oValue2) * this.sortDirection;
		if (iType == 2)
			return this.sortDirection;
		if (iType == 1)
			return -this.sortDirection;

		if (oValue1 < oValue2)
			return -this.sortDirection;
		else if (oValue1 > oValue2)
			return this.sortDirection;
		else
			return 0;
	}


	/**
	 * @publish
	 */
	btl.dataBinding.StringSorter = function() {
		btl.dataBinding.Sorter.call(this);
	}
	btl.dataBinding.StringSorter.prototype = new btl.dataBinding.Sorter();

	btl.dataBinding.StringSorter.prototype.compare = function(a, b) {
		var oValue1 = String(a).toLowerCase();
		var oValue2 = String(b).toLowerCase();
		if (oValue1 < oValue2)
			return -this.sortDirection;
		else if (oValue1 > oValue2)
			return this.sortDirection;
		else
			return 0;
	}


	/**
	 * @publish
	 */
	btl.dataBinding.NumberSorter = function() {
		btl.dataBinding.Sorter.call(this);
	}
	btl.dataBinding.NumberSorter.prototype = new btl.dataBinding.Sorter();

	btl.dataBinding.NumberSorter.prototype.compare = function(a, b) {
		var iType = 0;
		var oValue1 = parseFloat(a);
		if (isNaN(oValue1))
			iType = 1;

		var oValue2 = parseFloat(b);
		if (isNaN(oValue2))
			iType += 2;
		if (iType == 1)
			return this.sortDirection;
		if (iType == 2)
			return -this.sortDirection;

		return (oValue1 - oValue2) * this.sortDirection;
	}


	/**
	 * @publish
	 */
	btl.dataBinding.DateSorter = function() {
		btl.dataBinding.Sorter.call(this);
	}
	btl.dataBinding.DateSorter.prototype = new btl.dataBinding.Sorter();

	btl.dataBinding.DateSorter.prototype.compareIntRegExp = /^[0-9]+$/;

	btl.dataBinding.DateSorter.prototype.compare = function(a, b) {
		var oValue1 = this.compareIntRegExp.test(a) ? a : (new Date(a)).valueOf();
		if (isNaN(oValue1))
			oValue1 = (new Date()).valueOf();
		var oValue2 = this.compareIntRegExp.test(b) ? b : (new Date(b)).valueOf();
		if (isNaN(oValue2))
			oValue2 = (new Date()).valueOf();
		return (oValue1 - oValue2) * this.sortDirection;
	}

	btl.dataBinding.sorters = new btl.dataBinding.SorterList();

}

if (!btl.dataSource) {
	/**
	 * <p>The btl.dataSource API object.</p>
	 * <p>On this object, you will find data source API functions.</p>
	 * @publish
	*/
	btl.dataSource = {};

	/**
	 * Retrieves an array of formatters for the specified query. If the optional format argument
	 * is provided, it will also be parsed and included into the resulting array.
	 * @param {Element} source Reference to the dataSource element.
	 * @param {String} query Query string.
	 * @param {String} format Optional additional formatter.
	 * @return {Array} Formatters array.
	 * @publish
	 */
	btl.dataSource.getFormat = function btl_dataSource_getFormat(oSource, sQuery, vFormat){
		var aFormats = [];
		var vFieldFormat = btl.dataSource.getDataField(oSource, sQuery).format;
		if (vFieldFormat instanceof Array) {
			for (var i=0; vFieldFormat.length > i; i++)
				aFormats[i] = vFieldFormat[i];
		} else if (vFieldFormat) {
			aFormats = [vFieldFormat];
		}
		if (vFormat && vFormat.length){ //parse and append formats
			var arr = vFormat.split(']');
			for (var i=0; arr.length > i; i++){
			   if (arr[i].length){
					var arr2 = arr[i].split('[', 2);
					aFormats = aFormats.concat(arr2[0].replace(/^\s+/, '').split(/\s+/))
					if(arr2.length > 1){
						aFormats[aFormats.length-1] = aFormats[aFormats.length-1] + '['+arr2[1]+ ']'
					}
			   }//if
			}//for
		}//if
		return aFormats;
	}

	/**
	 * Retrieves a field value from the record that is specified by the id parameter.
	 * @param {Element} source Reference to the dataSource element.
	 * @param {String} id Record identifier.
	 * @param {String} query Query string.
	 * @param {Array} format Optional formats array.
	 *                       OR a string (single formatter to apply)
	 *                       OR false (no formatters are applied, not even defaults)
	 * @return {Variant} Field value.
	 * @publish
	 */
	btl.dataSource.getValue = function btl_dataSource_getValue(oSource, sId, sQuery, vFormat) {
		var oQuery = btl.dataSource.getDataField(oSource, sQuery);
		var oRecord = oSource._.oRecords.getRecord(sId);

		if ((!vFormat || !vFormat.length) && vFormat !== false && oQuery.format)
			vFormat = oQuery.format;
		var aFormat = !vFormat ? [] : vFormat instanceof Array ? vFormat : [vFormat];

		// XXX: check whether oRecord == null
		var sValue = oRecord.getFieldValue(oQuery);

		if (aFormat.length) {
			var sOriginalValue = sValue; //unformatted value
			var vRawValue,
				bRawDefined = false;
			for (var i = 0; i < aFormat.length; i++) {
			 	if (aFormat[i].length) {
			 		var oParsed = btl.dataBinding.formatters.parseFormatterString(aFormat[i]);
					var oFormatter = btl.dataBinding.formatters.getFormatter(oParsed.name);
					if (oFormatter) {
						if (oFormatter.raw) {
							if (!bRawDefined) {
								bRawDefined = true;
								vRawValue = oRecord.getSourceFieldValue(oQuery, true);
							}
							sValue = oFormatter.format(sValue, vRawValue, oParsed.parameters);
						} else
							sValue = oFormatter.format(sValue, sOriginalValue, oParsed.parameters);
					} else
						bb.command.trace(oSource, "Formatter " + oParsed.name + " is not found");
			 	}
			}
		}
		return sValue;
	}

	/**
	 * Sets a field value on the record that is specified by the id parameter.
	 * @param {Element} source Reference to the dataSource element.
	 * @param {String} id Record identifier.
	 * @param {String} query Query string.
	 * @param {Variant} value The value to be set.
	 * @return {String} The changed value of the field.
	 * @publish
	 */
	btl.dataSource.setValue = function btl_dataSource_setValue(oSource, sId, sQuery, vValue) {
		var oRecord = oSource._.oRecords.getRecord(sId);
		if (!oRecord) {
			bb.command.trace(oSource, "No such record is registered (id: " + sId + ")", 4);
			return;
		}
		var oQuery = btl.dataSource.getDataField(oSource, sQuery);
		oRecord.setFieldValue(oQuery, vValue);

		return vValue;
	}

	/**
	 * Returns a list of records related to the record that is specified by the id parameter.
	 * @param {Element} source Reference to the dataSource element.
	 * @param {String} id Record identifier.
	 * @param {String} fieldName The qualified field name that connects the records.
	 * @publish
	 */
	btl.dataSource.getLinked = function btl_dataSource_getLinked(oSource, sId, sField) {
		var arr = [];

		if (!('getRecords' in oSource.typeHandler)) {
			bb.command.trace(oSource, "Data type (" + oSource.dataType + ") does not support getRecords method.", 4);
			return arr;
		}
		if (btl.dataSource.getDataField(oSource, sField).dataSchema == '_self') { //get list of children of the record
			var oQuery = btl.dataSource.getDataField(oSource, sField);
			var oRecords = oSource.typeHandler.getRecords(oSource._.oRecords.getRecord(sId).source, oQuery);

			var oIdField = btl.dataSource.getDataField(oSource, 'identifier');

			for (var sName in oRecords) {
				if (oRecords.hasOwnProperty(sName)) {
					var sOrigRecId = oIdField.select ? btl.dataBinding.getRecordValue(oSource, oRecords[sName], oIdField) : sName;
					if (typeof sInd == 'string' && sInd == '') {
						btl.dataBinding.fireErrorEvent(oSource, 0, "Record id is empty");
						bb.command.trace(oSource, "Record id is empty");
					} else {
						var sRecId = oIdField.select ? sOrigRecId : sId + "." + sOrigRecId; //generated qualified record id or provided unique one
						oSource._.oRecords.addRecord(new btl.dataBinding.Record(oRecords[sName], oSource.typeHandler, sRecId, oIdField));
						arr[arr.length] = sRecId;
					}
				}
			}
		}
		return arr;
	}

	/**
	 * Performs an action request.
	 * @param {Object} source Reference to a dataObserver or dataSource controller object. If a dataSource element is passed, all its observers will be refreshed.
	 * @param {String} action Action to perform. In addition to the standard actions, a user can define custom actions.
	 * @paramvalue {action} read Reads elements from the datasource.
	 * @paramvalue {action} sort Sorts datasource elements.
	 * @paramvalue {action} update Updates datasource elements.
	 * @paramvalue {action} create Creates elements in the datasource.
	 * @paramvalue {action} delete Deletes elements from the datasource.
	 * @param {Object} parameters Optional parameters.
	 * @param {Boolean} clear The observer should be cleared.
	 * @example btl:btl_custom_actionRequest
	 * @return {Void} Nothing
	 * @publish
	 */
	btl.dataSource.actionRequest = function btl_dataSource_actionRequest(oController, sAction, vParameters, bClear) {
		if (!oController || !oController._)
			return;//destroyed

		var bIsObserver = bb.instanceOf(oController, btl.namespaceURI, 'dataObserver');
		var oSource = bIsObserver ? oController.getProperty('dataSource') : oController;
		if (!oSource  || !oSource._.bInitialized || !bb.instanceOf(oSource, btl.namespaceURI, 'iDataCommunicator'))
			return;

		// un-tangle variable parameters argument
		var vRecords, hAttributes;
		if (vParameters instanceof Object && !(vParameters instanceof Array)) {
			vRecords = vParameters.records || null;
			hAttributes = vParameters.attributes || {};
		} else {
			vRecords = vParameters;
			hAttributes = {};
		}
		if (vRecords instanceof Array && sAction != 'update' && vRecords[0] && (typeof vRecords[0] != 'object' || vRecords[0] instanceof String)) {
			hAttributes.records = vRecords;
			vRecords = null;
		}

		//Queue the request, other request might be busy...
		oSource._._requestQueue.push({
				isObserver: bIsObserver,
				controller: oController,
				action: sAction,
				parameters: vParameters,
				records: vRecords,
				attributes: hAttributes,
				clear: bClear
			});
		if (oSource._._requestQueue.length == 1)
			btl.dataSource.actionRequestQueued(oSource, bIsObserver, oController, sAction,
											vParameters, vRecords, hAttributes, bClear);
	}

	//Private functions
	btl.dataSource.requestQueueContinue = function btl_dataSource_requestQueueContinue(oSource) {
		oSource._._requestQueue.shift();
		if (oSource._._requestQueue.length) {
			var oSync        = oSource._._requestQueue[0];
			btl.dataSource.actionRequestQueued(oSource, oSync.isObserver, oSync.controller, oSync.action,
											oSync.parameters, oSync.records, oSync.attributes, oSync.clear);
		}
	}

	//Private functions
	btl.dataSource.actionRequestQueued = function btl_dataSource_actionRequestQueued(oSource, bIsObserver, oController, sAction, vParameters, vRecords, hAttributes, bClear) {
		if (bClear)
			oController.setProperty('indexes', []);

		if (!bIsObserver && (sAction == 'read' || sAction == 'sort')) {
			btl.dataBinding.fireErrorEvent(oSource, 0, 'Read action must be requested for one observer only.');
			bb.command.trace(oController, 'Read action must be requested for one observer only.', 2);
			return;
		}

		if (bIsObserver) {
	 		var oEvent = bb.document.createEvent('Events');
			oEvent.initEvent('actionRequest', false, false);
			oEvent.action = sAction;
			oEvent.data = vParameters;
			oController.dispatchEvent(oEvent);
		}

		var oJSONTypeHandler = btl.dataSource.types.getType('application/json');

		var oRecords = new btl.dataBinding.RecordList();
		var oAction = new btl.dataBinding.Action(sAction, oRecords);
		switch (sAction) {
			case 'update':
				var aUpdatedIds; // will contain list of record IDs that have outgoing changes (updated)
				if (vRecords) {
					// if records attribute passed, filter out records that have not changed
					aUpdatedIds = [];
					for (var i = 0, iMax = vRecords.length; i < iMax; i++) {
		 				var oRecord = oSource._.oRecords.getRecord(vRecords[i]);
		 				if (oRecord && oRecord.updated)
							aUpdatedIds[aUpdatedIds.length] = vRecords[i];
		 			}
				} else {
					// if no records attribute passed, update all records that have changed
					aUpdatedIds = oSource._.oRecords.getUpdatedRecordIds();
				}
				if (!aUpdatedIds.length) {
					// continue with next request if aList is empty (no outgoing changes)
					btl.dataSource.requestQueueContinue(oSource);
					return;
				}
				for (var i = 0, iLimit = aUpdatedIds.length; i < iLimit; i++) {
					var oRecord = oSource._.oRecords.getRecord(aUpdatedIds[i]);
					oRecords.addRecord(oRecord);
				}
				break;
			default:
				var oIdField = btl.dataSource.getDataField(oSource, 'identifier');
				if (vRecords) {
					if (vRecords instanceof Array)
						oRecords.serializeAsArray = true;
					for (var sId in vRecords) {
						if (vRecords.hasOwnProperty(sId)) {
							var oRecord = new btl.dataBinding.Record({}, oJSONTypeHandler, sId, null);
							for (var sFieldName in vRecords[sId]) {
								if (vRecords[sId].hasOwnProperty(sFieldName)) {
									var oDataField = btl.dataSource.getDataField(oSource, sFieldName);
									oRecord.setFieldValue(oDataField, vRecords[sId][sFieldName])
								}
							}
							oRecords.addRecord(oRecord);
						}
					}
				}
		}

		oJSONTypeHandler.unserializeAttributes(oAction, hAttributes);
		var oRequest = new btl.dataBinding.Request(oSource, oAction, bIsObserver ? oController : null);
		oRequest.send();
	}

	// If oObserver is a dataObserver control it will be refreshed
	// (except when it is a 'delete', 'update' or 'create' action)
	/**
	 * Applies an action, which is typically called upon server operation approval.
	 * It can also be called manually without waiting for a server response.
	 * @param {Object} source Reference to the dataSource controller object.
	 * @param {String} action Action performed.
	 * @param {Object} parameters Optional parameters object.
	 * @param {Element} observer Optional observer element.
	 * @return {Void} Nothing
	 * @publish
	 */
	btl.dataSource.actionApply = function btl_dataSource_actionApply(oSource, sAction, vParameters, oObserver) {
		if (!oSource || !oSource._)
			return;	//destroyed
		var oIdField = btl.dataSource.getDataField(oSource, 'identifier');
		switch (sAction) {
			case 'delete':
				if (vParameters instanceof Array) { // only array
					// delete removed records and their attributes
					for (var i = 0, iMax = vParameters.length; i < iMax; i++)
						if (oSource._.oRecords.hasRecord(vParameters[i])) {
							oSource._.oRecords.removeRecord(vParameters[i]);
							// XXX: Let removeRecord take care of updating the observers?
							var aObservers = oSource.getProperty('observers');
							for (var j = 0, iLimit = aObservers.length; iLimit > j; j++)
								if (vParameters[i] in aObservers[j]._.oIndexes)
									aObservers[j]._.oIndexes[vParameters[i]] = false;
						}
					// recreate dataSource array of existing indexes
					oSource._['_records'] = oSource._.oRecords.getRecordIds();
				}
				break;
			case 'create':
				if (vParameters instanceof Array) {//list of confirmed records
					//do nothing just confirm
				} else if (vParameters instanceof Object) { //actual data
					for (var sRecId in vParameters)
						if (vParameters.hasOwnProperty(sRecId)) {
							oSource._.oRecords.addRecord(new btl.dataBinding.Record(vParameters[sRecId], oSource.typeHandler, sRecId, oIdField));
						}
				}
				break;
			case 'update':
				if (vParameters instanceof Object) { // either array or object
					var aRecordIds = [];
					var hFields = {};

					if (vParameters instanceof Array) {//list of confirmed records
						for (var index in vParameters) {
							var sId = vParameters[index];
							if (vParameters.hasOwnProperty(index) && oSource._.oRecords.hasRecord(sId)) {
								var oRecord = oSource._.oRecords.getRecord(sId);
								oRecord.applyUpdates(hFields);
								aRecordIds[aRecordIds.length] = sId;
							}
						}
					} else {//records with new information
						for (var index in vParameters) {
							if (vParameters.hasOwnProperty(index)) {
								var oRecord = new btl.dataBinding.Record(vParameters[index], oSource.typeHandler, index, oIdField);
								oSource._.oRecords.addRecord(oRecord);
								oRecord.clearUpdates();
								aRecordIds[aRecordIds.length] = index;
							}
						}
					}
					if (aRecordIds.length) {
						var aObservers = oSource.getProperty('observers');
						for (var i = 0, iMax = aObservers.length; i < iMax; i++) {
							var sSortField = btl.dataSource.getDataField(oSource,
									aObservers[i].getAttribute('sortField')).name;
							for (var j = 0, jMax = aRecordIds.length; j < jMax; j++) {
								if (aRecordIds[j] in aObservers[i]._.oIndexes // record in a set
										&& sSortField in hFields) { // changed field is sort parameter
									aObservers[i].setProperty('sorted', false);
									break;
								}
							}
						}
					}
				}
				break;
			case 'select': // select some records with a condition
			case 'read':
			case 'sort':
				break;

		}//switch

		if (oObserver && oObserver._) {
				var oEvent = bb.document.createEvent('Events');
				oEvent.initEvent('actionResponse', false, false);
				oEvent.action = sAction;
				oEvent.data = vParameters;
					oObserver.dispatchEvent(oEvent);
		}
	}

	/**
	 * Registers a new data type or extends an existing one.
	 * @param {String} type Data type name (a MIME type).
	 * @param {Object} typeHandler Object implementing TypeHandler interface. Required methods are 'serialize', 'unserialize', 'getValue' and 'setValue'.
	 * @return {Object} On success, the TypeHandler object that was added; otherwise null.
	 * @publish
	 */
	btl.dataSource.addType = function btl_dataSource_addType(sType, oTypeHandler) {
		// XXX: for backwards compatibility
		return btl.dataSource.types.addType(sType, oTypeHandler);
	}

	/**
	 * Removes the data type.
	 * @param {String} type Data type name (a MIME type).
	 * @return {Object} Removed data type.
	 * @publish
	 */
	btl.dataSource.removeType = function btl_dataSource_removeType(sType) {
		// XXX: for backwards compatibility
		return btl.dataSource.types.removeType(sType);
	}


	/**
	 * This object keeps the types support, which are assigned by MIME types.
	 * @publish
	 */
	btl.dataBinding.TypeHandlerList = function() {
		this.types = {};

		var oXMLHandler = new btl.dataBinding.XMLTypeHandler();
		var oJSONHandler = new btl.dataBinding.JSONTypeHandler();

		this.addType('application/xml', oXMLHandler);
		this.addType('text/xml', oXMLHandler);
		this.addType('application/json', oJSONHandler);
		this.addType('text/json', oJSONHandler);
	}
	btl.dataBinding.TypeHandlerList.prototype = {};

	/**
	 * Registers a new data type or overwrites an existing one.
	 * @param {String} type Data type name (a MIME type).
	 * @param {Object} typeHandler Object implementing TypeHandler interface. Required methods are 'serialize', 'unserialize', 'getValue' and 'setValue'.
	 * @return {Object} On success, the TypeHandler object that was added; otherwise null.
	 */
	btl.dataBinding.TypeHandlerList.prototype.addType = function(sType, oTypeHandler) {
		if (btl.dataSource.testInterface(oTypeHandler, btl.dataBinding.TypeHandler)) {
			this.types[sType] = oTypeHandler;
			return oTypeHandler;
		} else
			bb.command.trace(null, "Can't add " + sType + " support: missing mandatory methods from TypeHandler interface.");
		return null;
	}

	btl.dataBinding.TypeHandlerList.prototype.getType = function(sType) {
		return this.types[sType];
	}

	/**
	 * Removes the data type.
	 * @param {String} type Data type name (a MIME type).
	 * @return {Object} Removed data type.
	 */
	btl.dataBinding.TypeHandlerList.prototype.removeType = function(sType) {
		if (this.types.hasOwnProperty(sType)) {
			var oTypeHandler = this.types[sType];
			delete this.types[sType];
			return oTypeHandler;
		}
		return null;
	}

	/**
	 * Tests whether an object implements an interface correctly.
	 * @return
	 * @publish
	 */
	btl.dataSource.testInterface = function(oObject, oInterface) {
		var oPrototype = oInterface.prototype;
		for (var i in oPrototype)
			if (oPrototype.hasOwnProperty(i) && (!(i in oObject) || oObject[i] == oPrototype[i]))
				return false;
		return true;
	}


	/**
	 * Interface for type objects
	 * @publish
	 */
	btl.dataBinding.TypeHandler = function() {
	}
	btl.dataBinding.TypeHandler.prototype = {};
	btl.dataBinding.TypeHandler.prototype.serialize = function(vData) {
		// implement
	}
	/**
	 *
	 * @param vData
	 * @param oIdField
	 * @param oDataSource
	 * @return
	 * @type btl.dataBinding.ActionGroup
	 */
	btl.dataBinding.TypeHandler.prototype.unserialize = function(vData, oIdField, oDataSource) {
		// implement
	}
	btl.dataBinding.TypeHandler.prototype.getValue = function(oRecord, oQuery, bRaw) {
		// implement
	}
	btl.dataBinding.TypeHandler.prototype.setValue = function(oRecord, oQuery, sValue) {
		// implement
	}


	/**
	 * XML type handler (application/xml)
	 * @publish
	 */
	btl.dataBinding.XMLTypeHandler = function() {
	}
	btl.dataBinding.XMLTypeHandler.prototype = {};

	btl.dataBinding.XMLTypeHandler.prototype.serialize = function(oActionGroup) {
//		if (typeof(oActionGroup) != 'object')
//			return ''; // XXX: fail silently?

		var oDocument = bb.xml.parse('<?xml version="1.0"?><request _actions_="true"/>');
		var oRootNode = oDocument.documentElement;

		var aActions = oActionGroup.getActionList();
		for (var i = 0; i < aActions.length; i++) {
			var oNode = this.serializeAction(aActions[i], oDocument);
			oRootNode.appendChild(oNode);
		}

		return bb.xml.serialize(oDocument);
	}

	btl.dataBinding.XMLTypeHandler.prototype.serializeAction = function(oAction, oDocument) {
		var oNode = oDocument.createElement(oAction.type);
		var hAttributes = oAction.getAttributeMap();
		for (var i in hAttributes)
			if (hAttributes.hasOwnProperty(i))
				oNode.setAttribute(i, String(hAttributes[i]));
		this.serializeRecordList(oAction.records, oNode, oDocument);
		return oNode;
	}

	btl.dataBinding.XMLTypeHandler.prototype.serializeRecordList = function(oRecordList, oNode, oDocument) {
		var oRecordsNode = oDocument.createElement('records');
		var aRecords = oRecordList.getRecordList();
		for (var i = 0, len = aRecords.length; i < len; i++)
			this.serializeRecord(aRecords[i], oRecordsNode, oDocument);
		if (oRecordsNode.firstChild)
			oNode.appendChild(oRecordsNode);
		return oNode;
	}

	btl.dataBinding.XMLTypeHandler.prototype.serializeRecord = function(oRecord, oNode, oDocument) {
		var oRecordNode = oDocument.createElement('record');
		var aFields = oRecord.getUpdatedFieldsList();
		var bIdentifier = false;
		for (var i = 0, len = aFields.length; i < len; i++) {
			this.serializeField(aFields[i], oRecordNode, oDocument);
			if (aFields[i].dataField.name == 'identifier')
				bIdentifier = true;
		}
		// make sure to add the identifier field as well
		if (!bIdentifier && oRecord.identifierField && oRecord.identifierField.dataField.select)
			this.serializeField(oRecord.identifierField, oRecordNode, oDocument);
		oNode.appendChild(oRecordNode);
		return oRecordNode;
	}

	btl.dataBinding.XMLTypeHandler.prototype.serializeField = function(oField, oNode, oDocument) {
		var sSelect = oField.dataField.select;
		var sValue = String(oField.value);
		if (sSelect.charAt(0) == '@') { // XXX: I don't think this XPath parsing is a good thing
			oNode.setAttribute(sSelect.substring(1), sValue);
		} else {
			var oFieldNode = oDocument.createElement(sSelect);
			var oTextNode = oDocument.createTextNode(sValue);
			oFieldNode.appendChild(oTextNode);
			oNode.appendChild(oFieldNode);
		}
	}

	btl.dataBinding.XMLTypeHandler.prototype.unserialize = function(vData, oIdField, oDataSource) {
		//sData - string, xml node, array of nodes (they must have the same ownerDocument)
		var oXml = typeof vData == 'string' ? bb.xml.parse(vData) : vData;
		var oActionGroup = new btl.dataBinding.ActionGroup();

		if (!oXml) // error in the response
			return oActionGroup;

		var oNode = oXml.nodeType == 9 ? oXml.documentElement : oXml;
		if ('length' in oXml) { // array or interface NodeList
			if (!oXml.length) {
				// TODO: move this if into unserializeAction...
				oActionGroup.addAction(new btl.dataBinding.Action('read', new btl.dataBinding.RecordList()));
			} else {
				oActionGroup.addAction(this.unserializeAction('read', oXml, oIdField));
			}
		} else {
			if (btl.isTrueValue("_actions_", oNode.getAttribute("_actions_"))) { // response with actions
				var aActions = bb.xml.selectNodes(oNode, '*');
				if (aActions.length) {
					for (var i = 0, iLimit = aActions.length; i < iLimit; i++) {
						if (aActions[i].nodeType == 1) {
							sAction = aActions[i].localName || aActions[i].tagName; // XXX: do not ignore namespace!
							oActionGroup.addAction(this.unserializeAction(sAction, aActions[i], oIdField));
						}
					}
				} else {
					var oErrorAction = this.unserializeAction('error', oNode, oIdField);
					if (oErrorAction && oErrorAction.hasAttribute('error'))
						oActionGroup.addAction(oErrorAction);
				}
			} else { //simple list of records
				var aNodes;
				if (oDataSource.hasAttribute('recordSelect'))
					aNodes = bb.xml.selectNodes(oNode, oDataSource.getAttribute('recordSelect'), oDataSource.modelNode);
				oActionGroup.addAction(this.unserializeAction('read', aNodes || oNode, oIdField));
			}
		}
		return oActionGroup;
	}

	btl.dataBinding.XMLTypeHandler.prototype.unserializeAction = function(sAction, vNode, oIdField) {
		// Get the records and put them in the data object
		var aNodes = 'length' in vNode ? vNode : bb.xml.selectNodes(vNode, '*');

		var oRecords = this.unserializeRecordList(aNodes, oIdField);
		var oAction = new btl.dataBinding.Action(sAction, oRecords)

		// XXX: shouldn't call unserializeAttributes with a node list...
		var oAttributes = this.unserializeAttributes(oAction, vNode);

		/*
		if (oAttributes.hasAttribute('records') && !aNodes.length) {
			// got list of records as attribute
			oRecords = new btl.dataBinding.RecordList();
			var aRecords = oAttributes.removeAttribute('records');
			for (var i = 0, len = aRecords.length; i < len; i++) {
				oRecords.addRecord(new btl.dataBinding.Record(null, null, aRecords[i], oIdField))
			}
		}
		*/
		return oAction;
	}

	btl.dataBinding.XMLTypeHandler.prototype.unserializeRecordList = function(aNodes, oIdField) {
		var oRecords = new btl.dataBinding.RecordList();
		if (oIdField.select) {
			// if the records have an ID
			for (var i = 0, iLimit = aNodes.length; i < iLimit; i++) {
				var oRecord = new btl.dataBinding.Record(aNodes[i], this, this.getValue(aNodes[i], oIdField), oIdField);
				oRecords.addRecord(oRecord);
			}
		} else {
			// if the records do not have an ID
			for (var i = 0, iLimit = aNodes.length; i < iLimit; i++) {
				var oRecord = new btl.dataBinding.Record(aNodes[i], this, i, oIdField);
				oRecords.addRecord(oRecord);
			}
		}
		return oRecords;
	}

	btl.dataBinding.XMLTypeHandler.prototype.unserializeAttributes = function(oAction, oNode) {
		var aAttributes = oNode.attributes;

		// Add attributes to attribute list
		for (var i = 0; aAttributes && i < aAttributes.length; i++) {
			var oAttribute = aAttributes.item(i),
				sName = oAttribute.nodeName,
				sValue = oAttribute.nodeValue;
			oAction.setAttributeString(sName, sValue);
		}
		return oAction;
	}

	btl.dataBinding.XMLTypeHandler.prototype.getValue = function(oRecord, oQuery, bRaw) {
		if (oRecord) {
			var oRes = bb.xml.selectSingleNode(oRecord, oQuery.select, oQuery.NSResolver);
			if (bRaw)
				return oRes;
			if (oRes && oRes.nodeType)
				// XXX: Why this serialize/replace?
				return bb.xml.serialize(oRes, true).replace(/<!\[CDATA\[(.*?)\]\]>/gm, '$1');
			else
				return oRes || '';
		}
		return bRaw ? oRecord : '';
	}

	btl.dataBinding.XMLTypeHandler.prototype.setValue = function(oRecord, oQuery, vValue) {
		if (oRecord) {
			var oRes = bb.xml.selectSingleNode(oRecord, oQuery.select, oQuery.NSResolver);
			if (oRes) {
				if (oRes.nodeType == 1) {
					if (oRes.firstChild && oRes.firstChild.nodeType == 3) {
						oRes.firstChild.nodeValue = vValue;
					} else {
						oRes.appendChild(oRecord.ownerDocument.createTextNode(vValue));
					}
				} else if (oRes.nodeType == 2) {
					oRes.nodeValue = vValue;
				}
			} else {
				try {
					// try to create the node from the XPath expression...
					if (oQuery.select.indexOf('@') == 0) {
						oRecord.setAttribute(oQuery.select.substr(1, oQuery.select.length), vValue);
					} else {
						// TODO: Add namespace support
						var oNew = oRecord.ownerDocument.createElement(oQuery.select);
						oNew.appendChild(oRecord.ownerDocument.createTextNode(vValue));
						oRecord.appendChild(oNew);
					}
				} catch (oError) {
					bb.command.trace(null, 'Error in xml.setValue: ' + oError, 3);
					return null;
				}
			}
			return vValue;
		}
		return null;
	}

	btl.dataBinding.XMLTypeHandler.prototype.getRecords = function(oRecord, oQuery) {
		var aRecords = [];
		if (oRecord) {
			var oRecordsList = bb.xml.selectNodes(oRecord, oQuery.select, oQuery.NSResolver || null);
			for (var i = 0; i < oRecordsList.length; i++)
				aRecords[aRecords.length] = oRecordsList[i];
		}
		return aRecords;
	}

	btl.dataBinding.XMLTypeHandler.prototype.toString = function() {
		return 'XMLTypeHandler';
	}

	/**
	 * JSON type handler (application/json)
	 * @publish
	 */
	btl.dataBinding.JSONTypeHandler = function() {
	}
	btl.dataBinding.JSONTypeHandler.prototype = {};

	btl.dataBinding.JSONTypeHandler.prototype.serialize = function(oActionGroup) {
		var hRoot = {};

		var aActions = oActionGroup.getActionList();
		for (var i = 0; i < aActions.length; i++)
			hRoot[aActions[i].type] = this.serializeAction(aActions[i]);

		return JSONRequest.serialize(hRoot);
	}

	btl.dataBinding.JSONTypeHandler.prototype.serializeAction = function(oAction) {
		var oJSONAction = {
				attributes: {},
				records: oAction.hasAttribute('records') ? oAction.getAttribute('records') :
						this.serializeRecordList(oAction.records)
		};
		var hAttributes = oAction.getAttributeMap();
		for (var i in hAttributes)
			if (hAttributes.hasOwnProperty(i) && i != 'records')
				oJSONAction.attributes[i] = hAttributes[i];
		return oJSONAction;
	}

	btl.dataBinding.JSONTypeHandler.prototype.serializeRecordList = function(oRecordList) {
		var aRecords = oRecordList.getRecordList();
		if (oRecordList.serializeAsArray) {
			var hRecords = [];
			for (var i = 0, len = aRecords.length; i < len; i++)
				hRecords[hRecords.length] = this.serializeRecord(aRecords[i], true);
		} else {
			var hRecords = {};
			for (var i = 0, len = aRecords.length; i < len; i++)
				hRecords[aRecords[i].identifier] = this.serializeRecord(aRecords[i], false);
		}
		return hRecords;
	}

	btl.dataBinding.JSONTypeHandler.prototype.serializeRecord = function(oRecord, bAddIdentifier) {
		var hRecord = {};
		var aFields = oRecord.getUpdatedFieldsList();
		var bIdentifier = false;
		for (var i = 0, len = aFields.length; i < len; i++) {
			hRecord[aFields[i].dataField.select] = this.serializeField(aFields[i]);
			if (aFields[i].dataField.name == 'identifier')
				bIdentifier = true;
		}
		// make sure to add the identifier field as well
		if (bAddIdentifier && !bIdentifier && oRecord.identifierField && oRecord.identifierField.dataField.select)
			hRecord[oRecord.identifierField.dataField.select] = this.serializeField(oRecord.identifierField);
		return hRecord;
	}

	btl.dataBinding.JSONTypeHandler.prototype.serializeField = function(oField) {
		return oField.value;
	}

	btl.dataBinding.JSONTypeHandler.prototype.unserialize = function(vData, oIdField, oDataSource) {
		var oData = typeof vData == 'string' ? JSONRequest.parse(vData) || [] : vData;

		var oActionGroup = new btl.dataBinding.ActionGroup();

		if (oData instanceof Array) {
			// only set of records
			var oAction = this.unserializeAction('read', oData, oIdField);
			oActionGroup.addAction(oAction);
		} else if (oData.hasOwnProperty('_actions_')) {
			delete oData['_actions_'];
			for (var sAction in oData) {
				if (oData.hasOwnProperty(sAction)) {
					var oAction = this.unserializeAction(sAction, oData[sAction], oIdField);
					oActionGroup.addAction(oAction);
				}
			}
		} else {
			// no actions, assume only records are there
			var oAction = this.unserializeAction('read', oData, oIdField);
			oActionGroup.addAction(oAction);
		}
		return oActionGroup;
	}

	btl.dataBinding.JSONTypeHandler.prototype.unserializeAction = function(sAction, oObject, oIdField) {
		var oAction = new btl.dataBinding.Action(sAction);
		var oRecordList = null;
		if (oObject instanceof Array) { // only set of records
			oRecordList = this.unserializeRecordList(oObject, oIdField);
		} else if (oObject.hasOwnProperty('error')) {
			oAction.setAttribute('error', oObject.error);
		} else {
			if (oObject.hasOwnProperty('records') && !(oObject.records instanceof Array && (typeof oObject.records[0] != 'object' || oObject.records[0] instanceof String))) {
				oRecordList = this.unserializeRecordList(oObject.records, oIdField);
				delete oObject.records;
			}
			if (oObject.hasOwnProperty('attributes')) {
				this.unserializeAttributes(oAction, oObject.attributes);
			} else {
				this.unserializeAttributes(oAction, oObject);
			}
		}
		oAction.setRecords(oRecordList || new btl.dataBinding.RecordList());
		return oAction;
	}

	btl.dataBinding.JSONTypeHandler.prototype.unserializeRecordList = function(oObject, oIdField) {
		var oRecordList = new btl.dataBinding.RecordList();

		for (var i in oObject) {
			if (oObject.hasOwnProperty(i)) {
				var oRecord = new btl.dataBinding.Record(oObject[i], this, i, oIdField);
				oRecordList.addRecord(oRecord);
			}
		}
		return oRecordList;
	}

	btl.dataBinding.JSONTypeHandler.prototype.unserializeAttributes = function(oAction, oObject) {
		for (var i in oObject)
			if (oObject.hasOwnProperty(i))
				oAction.setAttribute(i, oObject[i]);
		return oAction;
	}

	btl.dataBinding.JSONTypeHandler.prototype.getValue = function(oRecord, oQuery, bRaw) {
		// if oRecord is an object, return oQuery result, otherwise oRecord itself, otherwise ""
		var sQuery = oQuery.select;
		if (oRecord instanceof Object && sQuery) {
			if (sQuery.indexOf('.') != -1) {
				var aQuery = sQuery.split('.');
				for (var i = 0; i < aQuery.length && oRecord; i++)
					oRecord = oRecord[aQuery[i]];
			} else
				oRecord = oRecord[sQuery];
		}
		return oRecord != null ? oRecord : '';
	}

	btl.dataBinding.JSONTypeHandler.prototype.setValue = function(oRecord, oQuery, sValue) {
		if (!oRecord)
			return null;
		return oRecord[oQuery.select] = sValue;
	}

	btl.dataBinding.JSONTypeHandler.prototype.getRecords = function(oRecord, oQuery) {
		if (!oRecord)
			return [];
		var oRes = oRecord[oQuery.select];
		return oRes instanceof Object ? oRes : [];
	}

	btl.dataBinding.JSONTypeHandler.prototype.toString = function() {
		return 'JSONTypeHandler';
	}



	/**
	 * <p>The btl.dataSource.types object.</p>
	 * <p>This object keeps the types support, which are assigned by MIME types.</p>
	 */
	btl.dataSource.types = new btl.dataBinding.TypeHandlerList();


	/**
	 * A list of Formatters
	 * @publish
	 */
	btl.dataBinding.FormatterList = function() {
		this.formatters = {};
		this.parseRegexp = /\s*([^\[]+)\s*(?:\[(.*)\])?/;

		this.addFormatter('innerXml',   new btl.dataBinding.InnerXMLFormatter());
		this.addFormatter('raw',        new btl.dataBinding.RawFormatter());
		this.addFormatter('currency',   new btl.dataBinding.CurrencyFormatter());
		this.addFormatter('style',      new btl.dataBinding.StyleFormatter());
		this.addFormatter('htmlescape', new btl.dataBinding.HTMLEscapeFormatter());
	}
	btl.dataBinding.FormatterList.prototype = {};

	/**
	 * Registers the formatter. If there was an existing one for the specified type, it will be replaced.
	 * @param {String} name Formatter name.
	 * @param {Function} formatter Formatter function. Parameters are formattedValue, value (optional), parameters (optional).
	 * @param {Boolean} raw Optional flag indicating whether the data to be passed should be raw.
	 * @return {Function} Previously registered formatter.
	 * @example btl:btl_dataSource_addFormatter
	 * @publish
	 */
	btl.dataBinding.FormatterList.prototype.addFormatter = function(sName, oFormatter) {
		var oOldFormatter = this.getFormatter(sName);
		this.formatters[sName] = oFormatter;
		return oOldFormatter;
	}

	btl.dataBinding.FormatterList.prototype.getFormatter = function(sName) {
		return this.formatters[sName] || null;
	}

	/**
	 * Parses a formatter string that contains a formatter name and parameters
	 * @param sFormatterString The string to parse
	 * @return An object with two String fields, 'name' and 'parameters'.
	 * @type Object
	 */
	btl.dataBinding.FormatterList.prototype.parseFormatterString = function(sFormatterString) {
		var aMatches = this.parseRegexp.exec(sFormatterString);
		return { name: aMatches[1], parameters: aMatches[2] };
	}

	/**
	 * Unregisters the formatter and returns it.
	 * @param {String} name Formatter name.
	 * @return {Function} Unregistered formatter.
	 * @publish
	 */
	btl.dataBinding.FormatterList.prototype.removeFormatter = function(sName) {
		var oOldFormatter = this.getFormatter(sName);
		delete this.formatters[sName];
		return oOldFormatter;
	}


	/**
	 * Base class for formatters.
	 * @param bRaw
	 * @publish
	 */
	btl.dataBinding.Formatter = function() {
	}
	btl.dataBinding.Formatter.prototype = {};

	btl.dataBinding.Formatter.prototype.raw = false;

	btl.dataBinding.Formatter.prototype.format = function(sFormattedValue, sValue, sParameters) {
		// implement
	}


	/**
	 * @publish
	 */
	btl.dataBinding.InnerXMLFormatter = function() {
		btl.dataBinding.Formatter.call(this);
	}
	btl.dataBinding.InnerXMLFormatter.prototype = new btl.dataBinding.Formatter();

	btl.dataBinding.InnerXMLFormatter.prototype.format = function(sFormattedValue, vValue) {
		// XXX: interface says second parameter is string!
		return vValue && vValue.nodeType ? bb.xml.serialize(vValue, true) : vValue;
	}


	/**
	 * @publish
	 */
	btl.dataBinding.RawFormatter = function() {
		btl.dataBinding.Formatter.call(this);
	}
	btl.dataBinding.RawFormatter.prototype = new btl.dataBinding.Formatter();

	btl.dataBinding.RawFormatter.prototype.format = function(sFormattedValue, vValue) {
		// XXX: interface says second parameter is string!
		return vValue;
	}


	/**
	 * @publish
	 */
	btl.dataBinding.CurrencyFormatter = function() {
		btl.dataBinding.Formatter.call(this);
	}
	btl.dataBinding.CurrencyFormatter.prototype = new btl.dataBinding.Formatter();

	btl.dataBinding.CurrencyFormatter.prototype.format = function(sFormattedValue, vValue, sParameters) {
		// XXX: interface says second parameter is string!
		if (!sFormattedValue || !sFormattedValue.length)
			return sFormattedValue;
		switch(sParameters) {
		case 'euro':
			return "&euro;" + sFormattedValue;
		case 'pound':
			return "&pound;" + sFormattedValue;
		case 'yen':
			return "&yen;" + sFormattedValue;
		case 'cent':
			return "&cent;" + sFormattedValue;
		default:
			return "$" + sFormattedValue;
		}
	}


	/**
	 * @publish
	 */
	btl.dataBinding.StyleFormatter = function() {
		btl.dataBinding.Formatter.call(this);
	}
	btl.dataBinding.StyleFormatter.prototype = new btl.dataBinding.Formatter();

	btl.dataBinding.StyleFormatter.prototype.format = function(sFormattedValue, vValue, sStyle) {
		// XXX: interface says second parameter is string!
		return "<span style='" + sStyle + "'>" + sFormattedValue + "</span>";
	}


	/**
	 * @publish
	 */
	btl.dataBinding.HTMLEscapeFormatter = function() {
		btl.dataBinding.Formatter.call(this);
	}
	btl.dataBinding.HTMLEscapeFormatter.prototype = new btl.dataBinding.Formatter();

	btl.dataBinding.HTMLEscapeFormatter.prototype.format = function(sFormattedValue, vValue, sStyle) {
		// XXX: interface says second parameter is string!
		return bb.string.escapeXml(sFormattedValue);
	}


	//data formatters
	btl.dataBinding.formatters = new btl.dataBinding.FormatterList();



	/**
	 * Registers the formatter. If there was an existing one for the specified type, it will be replaced.
	 * @param {String} name Formatter name.
	 * @param {Function} formatter Formatter function. Parameters are formattedValue, value (optional), parameters (optional).
	 * @param {Boolean} raw Optional flag indicating whether the data to be passed should be raw.
	 * @return {Function} Previously registered formatter.
	 * @example btl:btl_dataSource_addFormatter
	 * @publish
	 */
	btl.dataSource.addFormatter = function btl_dataSource_addFormatter(sName, fFormatter, bRaw) {
		// XXX: for backwards compatibility
		var oFormatter = new btl.dataBinding.Formatter();
		oFormatter.raw = bRaw;
		oFormatter.format = fFormatter;
		oOldFormatter = btl.dataBinding.formatters.addFormatter(sName, oFormatter);
		return oOldFormatter ? oOldFormatter.format : null;
	}

	/**
	 * Unregisters the formatter and returns it.
	 * @param {String} name Formatter name.
	 * @return {Function} Unregistered formatter.
	 * @publish
	 */
	btl.dataSource.removeFormatter = function btl_dataSource_removeFormatter(sName){
		// XXX: for backwards compatibility
		oOldFormatter = btl.dataBinding.formatters.removeFormatter(sName);
		return oOldFormatter ? oOldFormatter.format : null;
	}

	/**
	 * Generates a hash array with attribute names as keys and controller attribute values as values.
	 * @param {Element} observer Reference to the observer element.
	 * @param {Array} attributes Array of attribute names.
	 * @param {Boolean} translate When set to true, the names of attributes will be translated with the controller schema.
	 * @return {Object} The object that was retrieved from the specified attribute list.
	 * @publish
	 */
	btl.dataSource.getObjectFromAttributes = function btl_dataSource_getObjectFromAttributes(oController, aAttrList, bTranslate){
		// XXX: it translates attribute values by dataSchema from dataSource of the oController if bTranslate is true
		var obj = {};
		var oSource = bTranslate ? oController.getProperty('dataSource') : null;

		if (aAttrList instanceof Array && aAttrList.length)
			for (var i=0, iMax = aAttrList.length; iMax > i; i++){
				obj[aAttrList[i]] = btl.dataSource.getDataField(oSource,
						oController.getAttribute(aAttrList[i])).select;
			}
		return obj;
	}

	/**
	 * Splits a space, comma or semicolon-separated string into an array.
	 * @param {String} value The string to split. If this is not a string, it will be returned as-is.
	 * @return An array containing strings.
	 * @type Array
	 */
	btl.dataSource.split = function btl_dataSource_split(sValue) {
		if (typeof sValue == 'string') {
			var aReturn = [];
			var aValue = sValue.split(/\s|;|,/);
			for (var i = 0, len = aValue.length; i < len; i++) {
				var sItem = aValue[i].replace(/^\s*(.*)\s*$/, '$1');
				if (sItem.length)
					aReturn[aReturn.length] = sItem;
			}
			return aReturn;
		} else {
			return sValue;
		}
	};

 	/**
	 * Checks if the record with the specified id exists in the dataSource.
 	 * @param {Element} source Reference to the dataSource element.
	 * @param {String} id Record identifier.
	 * @type {Boolean}
	 * @publish
	 */
	btl.dataSource.recordExists = function btl_dataSource_recordExists(oSource, sId) {
		return oSource._.oRecords.hasRecord(sId);
	}

	/**
	 * While processing an action request response, returns whether an existing record was updated.
	 * @param {Element} source Reference to the dataSource element.
	 * @param {String} id Record identifier.
	 * @type {Boolean}
	 * @publish
	 */
	btl.dataSource.recordChanged = function btl_dataSource_recordChanged(oSource, sId) {
		return oSource._.oRecords.hasChangedRecord(sId);
	}


	/**
	 * A DataSchema.
	 * @publish
	 */
	btl.dataBinding.DataSchema = function() {
		this.dataFields = {};
	}
	btl.dataBinding.DataSchema.prototype = {};

	btl.dataBinding.DataSchema.prototype.toString = function() {
		return 'DataSchema';
	}

	btl.dataBinding.DataSchema.prototype.init = function(oSchema) {
		if (oSchema._.bInitialized)
			return this;
		var aChildren = oSchema.getProperty('childNodes');
		for (var i = 0, iMax = aChildren.length; iMax > i; i++)
			if (bb.instanceOf(aChildren[i], btl.namespaceURI, 'dataField'))
				this.setField(aChildren[i].createDataFieldObject());
		oSchema._.bInitialized = true;
		return this;
	}

	btl.dataBinding.DataSchema.prototype.getFieldsList = function() {
		var aFields = [];
		for (var i in this.dataFields)
			if (this.dataFields.hasOwnProperty(i))
				aFields[aFields.length] = this.dataFields[i];
		return aFields;
	}

	btl.dataBinding.DataSchema.prototype.getFieldsMap = function() {
		return this.dataFields;
	}

	btl.dataBinding.DataSchema.prototype.getField = function(sName) {
		return this.dataFields[sName] || null;
	}

	btl.dataBinding.DataSchema.prototype.setField = function(oField) {
		this.dataFields[oField.name] = oField;
	}

	btl.dataBinding.DataSchema.prototype.removeField = function(sName) {
		if (!this.dataFields.hasOwnProperty(sName))
			return null;
		var oField = this.dataFields[sName];
		delete this.dataFields[sName];
		return oField;
	}



	/**
	 * Constructs a DataField object representing a dataField.
	 * @param name The name of the field.
	 * @param select The selection mapping for the field (optional, default equals name value)
	 * @param NSResolver The namespace resolver for the selection (only applies to XML) (optional, default null)
	 * @param format The formatter for the field (optional, default null)
	 * @param type The datatype for the field (optional, default 'default')
	 * @param dataSchema The dataSchema name that the field is referencing (optional, default null)
	 *
	 * @class DataField object, representing a field in the data.
	 * An instance of the DataField object has the following public properties:
	 * name, select, NSResolver, format, type, dataSchema.
	 * These are copied from the constructor parameters.
	 * @publish
	 */
	btl.dataBinding.DataField = function(sName, sSelect, oNSResolver, vFormat, sType, sDataSchema) {
		this.name = sName;
		this.select = sSelect != null ? sSelect : sName;
		this.NSResolver = oNSResolver || null;
		this.format = vFormat || null;
		this.type = sType || 'default';
		this.dataSchema = sDataSchema || null;
	}
	btl.dataBinding.DataField.prototype = {};



	/**
	 * Return a DataField object from the dataSchema,
	 * or, if it does not exist, return a new DataField object.
	 * @param dataSource The dataSource on which the dataSchema is specified
	 * @param name The name of the field to find
	 * @param NSResolver The NSResolver to use for the name in case the the field can not be found (optional)
	 * @return A DataField object
	 * @type DataField
	 * @publish
	 */
	btl.dataSource.getDataField = function btl_dataSource_getDataField(oDataSource, sName, oNSResolver) {
		// TODO: it was decided to throw an error/warning instead of returning a new DataField
		return oDataSource && oDataSource._.dataSchema.getField(sName) || new btl.dataBinding.DataField(sName, sName, oNSResolver);
	}



	/**
	 * Constructs an updated record field object
	 * @param dataField A DataField object instance which indicates the data field
	 * @param value The new value for the field
	 *
	 * @class UpdatedField object, representing an updated field in a record.
	 * An instance of the DataField object has the following public properties:
	 * dataField, value. These are copied from the constructor parameters.
	 * @publish
	 */
	btl.dataBinding.Field = function(oDataField, vValue) {
		this.dataField = oDataField;
		this.value = vValue;
	}
	btl.dataBinding.Field.prototype = {};



	/**
	 * Constructs a record object
	 * @param sourceRecord The source record object (JSON/XML)
	 * @param typeHandler A TypeHandler object to process the source record
	 * @param identifier The identifier of the record
	 * @param identifierField The identifier field object
	 * @class Record object, representing a data bound record.
	 * An instance of the Record object has the following public properties:
	 * identifier, source, updated.
	 * @publish
	 */
	btl.dataBinding.Record = function(oSourceRecord, oTypeHandler, sIdentifier, oIdentifierField) {
		this.source = oSourceRecord || null;
		this.typeHandler = oTypeHandler || null;
		this.fields = {};
		this.updated = false;
		this.updatedFields = {};

		this.identifier = sIdentifier != null ? sIdentifier : null;

		if (sIdentifier != null && oIdentifierField) {
			// add identifier field
			this.identifierField = new btl.dataBinding.Field(oIdentifierField, sIdentifier);
			this.fields[oIdentifierField.name] = this.identifierField;
		} else {
			this.identifierField = null;
		}
	}
	btl.dataBinding.Record.prototype = {};

	btl.dataBinding.Record.prototype.setFieldValue = function(oDataField, sValue) {
		this.updated = true;
		this.updatedFields[oDataField.name] = new btl.dataBinding.Field(oDataField, sValue);
	}

	btl.dataBinding.Record.prototype.setSourceFieldValue = function(oDataField, sValue) {
		return this.typeHandler.setValue(this.source, oDataField, sValue);
	}

	btl.dataBinding.Record.prototype.getFieldValue = function(oDataField, bRaw) {
		var oField = this.getField(oDataField);
		return oField ? oField.value : this.getSourceFieldValue(oDataField, bRaw);
	}

	btl.dataBinding.Record.prototype.getSourceFieldValue = function(oDataField, bRaw) {
		var vValue = this.typeHandler.getValue(this.source, oDataField, bRaw);
		// cache the value in a Field object (if not raw)
//		if (!bRaw)
//			this.fields[oDataField.name] = new btl.dataBinding.Field(oDataField, vValue);
		return vValue;
	}

	/**
	 * Get a Field object by name
	 * @param oDataField
	 * @return
	 */
	btl.dataBinding.Record.prototype.getField = function(oDataField) {
		var sName = oDataField.name;
		return (this.updatedFields.hasOwnProperty(sName) && this.updatedFields[sName]) ||
				(this.fields.hasOwnProperty(sName) && this.fields[sName]) || null;
	}

	/**
	 * Gets a list of all fields.
	 * @return A list of all fields
	 * @type Array
	 */
	btl.dataBinding.Record.prototype.getFieldsList = function() {
		var aList = this.getUpdatedFieldsList();
		for (var i in this.fields)
			if (this.fields.hasOwnProperty(i) && !this.updatedFields.hasOwnProperty(i))
				aList[aList.length] = this.fields[i];
		return aList;
	}

	/**
	 * Gets a list of all updated fields.
	 * @return A list of all updated fields
	 * @type Array
	 */
	btl.dataBinding.Record.prototype.getUpdatedFieldsList = function() {
		var aList = [];
		for (var i in this.updatedFields)
			if (this.updatedFields.hasOwnProperty(i))
				aList[aList.length] = this.updatedFields[i];
		return aList;
	}

	/**
	 * Clears all UpdatedFields from a record object.
	 */
	btl.dataBinding.Record.prototype.clearUpdates = function() {
		this.updated = false;
		this.updatedFields = {};
	}

	/**
	 *
	 * @param hFields Pre-existing hash object to update with modified fields (optional).
	 * @return Hash object containing the updated fields as key values.
	 */
	btl.dataBinding.Record.prototype.applyUpdates = function(hFields) {
		hFields = hFields || {};
		for (var i in this.updatedFields) {
			if (this.updatedFields.hasOwnProperty(i)) {
				var oField = this.updatedFields[i];
				this.setSourceFieldValue(oField.dataField, oField.value);
//				this.fields[i] = oField;
				hFields[oField.dataField.name] = true;
			}
		}
		this.clearUpdates();
		return hFields;
	}

	/**
	 * Copies the UpdatedFields from a record object.
	 * @param record The source record
	 */
	btl.dataBinding.Record.prototype.copyUpdates = function(oRecord) {
		this.updated = oRecord.updated;
		this.updatedFields = oRecord.updatedFields;
	}

	btl.dataBinding.Record.prototype.toString = function() {
		return 'Record (id: ' + this.identifier + ', updated: ' + this.updated + ', type: ' + this.typeHandler + ')';
	}



	/**
	 * Constructs a RecordList object containing records.
	 * @class This class represents a list of data bound records.
	 * An instance of this object has the following public properties:
	 * records, size.
	 * @publish
	 */
	btl.dataBinding.RecordList = function() {
		this.records = {};
		this.size = 0;
		this.changedRecords = {};
		this.serializeAsArray = false;	// XXX: dirty way to retain back compat - see bug 11626 comment 3
	}
	btl.dataBinding.RecordList.prototype = {};

	/**
	 * Adds a record to the RecordList.
	 * When a record with the same identifier already exists, the updates are carried over, and the record is replaced.
	 * @param record The Record object to add.
	 */
	btl.dataBinding.RecordList.prototype.addRecord = function(oRecord) {
		if (this.records.hasOwnProperty(oRecord.identifier)) {
			oRecord.copyUpdates(this.records[oRecord.identifier]);
			this.changedRecords[oRecord.identifier] = true;
		} else
			this.size++;
		this.records[oRecord.identifier] = oRecord;
	}

	/**
	 * Clears the list of changed records.
	 */
	btl.dataBinding.RecordList.prototype.clearChangedRecords = function() {
		this.changedRecords = {};
	}

	/**
	 * Returns whether the identified record is in the list of changed records.
	 */
	btl.dataBinding.RecordList.prototype.hasChangedRecord = function(sId) {
		return this.changedRecords.hasOwnProperty(sId);
	}

	/**
	 * Gets a record from the RecordList.
	 * @param id The identifier of the record.
	 * @return The Record object.
	 * @type Record
	 */
	btl.dataBinding.RecordList.prototype.getRecord = function(sId) {
		return this.records[sId] || null;
	}

	/**
	 * Returns whether the RecordList has a certain record.
	 * @param id The identifier of the record.
	 * @return <code>true</code> if the RecordList contains the record.
	 * @type Boolean
	 */
	btl.dataBinding.RecordList.prototype.hasRecord = function(sId) {
		return this.records.hasOwnProperty(sId);
	}

	/**
	 * Removes a record from the RecordList.
	 * @param id The identifier of the record.
	 * @return The Record object that was removed.
	 * @type Record
	 */
	btl.dataBinding.RecordList.prototype.removeRecord = function(sId) {
		if (this.records.hasOwnProperty(sId)) {
			var oRecord = this.records[sId];
			delete this.records[sId];
			this.size--;
			return oRecord;
		} else
			return null;
	}

	btl.dataBinding.RecordList.prototype.getRecordList = function() {
		var aRecords = [];
		for (var i in this.records)
			if (this.records.hasOwnProperty(i))
				aRecords[aRecords.length] = this.records[i];
		return aRecords;
	}

	btl.dataBinding.RecordList.prototype.getRecordMap = function() {
		return this.records;
	}

	/**
	 * Gets a list of record identifiers that are in the RecordList.
	 * @return List of record identifiers.
	 * @type Array
	 */
	btl.dataBinding.RecordList.prototype.getRecordIds = function() {
		var aList = [];
		for (var sId in this.records)
			if (this.records.hasOwnProperty(sId))
				aList[aList.length] = sId;
		return aList;
	}

	/**
	 * Gets a list of record identifiers that are in the RecordList and have the updated flag.
	 * @return List of record identifiers.
	 * @type Array
	 */
	btl.dataBinding.RecordList.prototype.getUpdatedRecordIds = function() {
		var aList = [];
		for (var sId in this.records)
			if (this.records.hasOwnProperty(sId) && this.records[sId].updated)
				aList[aList.length] = sId;
		return aList;
	}

	btl.dataBinding.RecordList.prototype.toString = function() {
		return 'RecordList (size: ' + this.size + ')';
	}



	/**
	 * Constructs a RecordCache object containing records, and a cache pruning mechanism.
	 * @class This class represents a list of data bound records, and a cache pruning mechanism.
	 * An instance of this object has the following public properties:
	 * records, size, cacheLimit.
	 * @publish
	 */
	btl.dataBinding.RecordCache = function() {
		btl.dataBinding.RecordList.call(this);
		this.cacheLimit = Infinity;
	}
	btl.dataBinding.RecordCache.prototype = new btl.dataBinding.RecordList();

	/**
	 * Sets the cache limit of the RecordList
	 * @param {Number} cacheLimit The new value for the cache limit
	 */
	btl.dataBinding.RecordCache.prototype.setCacheLimit = function(iCacheLimit) {
		this.cacheLimit = iCacheLimit;
		// XXX: call prune here?
	}

	/**
	 * Prunes the RecordList. All records that are not in the indexes list of any of the
	 * provided observers, nor have pending updates, will be removed from the RecordList.
	 * @param {Array} observers The observers that use this RecordList.
	 */
	btl.dataBinding.RecordCache.prototype.prune = function(aObservers) {
		if (this.cacheLimit < this.size) {
			// make list of records that are in use (can not be pruned)
			var hAllRecords = {};
			for (var i = 0, iLimit = aObservers.length; i < iLimit; i++) {
				var aIndexes = aObservers[i].getProperty('indexes');
				for (var j = 0, jLimit = aIndexes.length; j < jLimit; j++)
					hAllRecords[aIndexes[j]] = true;
			}
			// remove all unused records
			for (var sRecordId in this.records)
				if (this.records.hasOwnProperty(sRecordId) && !hAllRecords.hasOwnProperty(sRecordId) && !this.records[sRecordId].updated)
					this.removeRecord(sRecordId);
		}
	}

	btl.dataBinding.RecordCache.prototype.toString = function() {
		return 'RecordCache (size: ' + this.size + ', cache limit: ' + this.cacheLimit + ')';
	}



	/**
	 * A group of actions
	 * @publish
	 */
	btl.dataBinding.ActionGroup = function() {
		this.actions = {};
	}
	btl.dataBinding.ActionGroup.prototype = {};

	btl.dataBinding.ActionGroup.prototype.addAction = function(oAction) {
		this.actions[oAction.type] = oAction;
	}

	btl.dataBinding.ActionGroup.prototype.getAction = function(sAction) {
		return this.actions[sAction] || null;
	}

	btl.dataBinding.ActionGroup.prototype.getActionList = function() {
		var aActions = [];
		for (var i in this.actions)
			if (this.actions.hasOwnProperty(i))
				aActions.push(this.actions[i]);
		return aActions;
	}

	btl.dataBinding.ActionGroup.prototype.toString = function() {
		return 'ActionGroup';
	}


	/**
	 * An action.
	 * Note that every action must have a RecordList, so if you pass null to the constructor,
	 * you must ensure to set a RecordList before the object is used using the setRecords() method.
	 * Public properties:
	 * - records, containing a RecordList.
	 * @param sAction The action type (create/read/update/delete)
	 * @param oRecordList A list of records (optional).
	 * @publish
	 */
	btl.dataBinding.Action = function(sAction, oRecordList) {
		this.type = sAction;
		this.records = oRecordList || null;
		this.attributes = {};
	}
	btl.dataBinding.Action.prototype = {};

	btl.dataBinding.Action.prototype.toString = function() {
		return 'Action (' + this.type + ')';
	}

	/**
	 * Sets the Action's records to the provided RecordList instance.
	 * @param oRecordList A RecordList instance.
	 */
	btl.dataBinding.Action.prototype.setRecords = function(oRecordList) {
		return this.records = oRecordList;
	}

	btl.dataBinding.Action.prototype.setAttribute = function(sName, vValue) {
		return this.attributes[sName] = vValue;
	}

	btl.dataBinding.Action.prototype.getAttribute = function(sName) {
		return this.attributes[sName];
	}

	btl.dataBinding.Action.prototype.hasAttribute = function(sName) {
		return this.attributes.hasOwnProperty(sName);
	}

	btl.dataBinding.Action.prototype.removeAttribute = function(sName) {
		var vValue = this.attributes[sName];
		delete this.attributes[sName];
		return vValue;
	}

	/**
	 * Returns the hash object with attribute key/values
	 * @return
	 */
	btl.dataBinding.Action.prototype.getAttributeMap = function() {
		return this.attributes;
	}

	/**
	 * Overwrites the action attributes with the provided attribute hash object
	 * @param hAttributes A hash object containing attribute key/values in their required type.
	 */
	btl.dataBinding.Action.prototype.setAttributeMap = function(hAttributes) {
		return this.attributes = hAttributes;
	}

	/**
	 * Sets an attribute from a string value, which it casts to the desired type.
	 * @param {String} sName Attribute name
	 * @param {String} sValue Attribute value
	 * @return The attribute value set.
	 */
	btl.dataBinding.Action.prototype.setAttributeString = function(sName, sValue) {
		switch (sName) {
		case 'records':
		case 'indexes':
			sValue = btl.dataSource.split(sValue);
			break;
		case 'totalRecords':
		case 'rangeStart':
		case 'rangeEnd':
			sValue = parseInt(sValue, 10);
			break;
		}
		return this.attributes[sName] = sValue;
	}


	/**
	 * An object representing a request
	 * @param oAction A btl.dataBinding.Action instance to be serialized and transmitted with the request.
	 * @param oObserver The request observer (optional). When omitted, <em>all</em> observers registered to
	 *                  the dataSource will be notified.
	 */
	btl.dataBinding.Request = function(oDataSource, oAction, oObserver) {
		this.dataSource = oDataSource;
		this.action = oAction;
		this.observer = oObserver;
	}
	btl.dataBinding.Request.prototype = {};

	btl.dataBinding.Request.prototype.toString = function() {
		return 'Request';
	}

	btl.dataBinding.Request.prototype.send = function() {
		this.dataSource.sendRequest(this.action, this.observer);
	}



	btl.dataBinding.Response = function(oActionGroup, oRequest) {
		this.actionGroup = oActionGroup;
		this.request = oRequest
	}
	btl.dataBinding.Response.prototype = {};

	btl.dataBinding.Response.prototype.toString = function() {
		return 'Response';
	}

}]]></d:resource>

			<d:attribute name="dataSource">
				
				<d:changer type="text/javascript"><![CDATA[
					if (this._.bInitialized) {
						// re-register
						btl.dataBinding.dataSources.unregisterObserver(this);
						btl.dataBinding.dataSources.registerObserver(this);
					}
				]]></d:changer>
			</d:attribute>

			<d:attribute name="rows">
				
			</d:attribute>

			<d:attribute name="sortDirection" default="ascending">
				
			</d:attribute>

			<d:attribute name="sortField">
				
			</d:attribute>

			<d:property name="dataSource">
				
				<d:getter type="text/javascript"><![CDATA[
					return this._._dataSource || null;
				]]></d:getter>
			</d:property>

			<d:method name="getDataSource">
				
				<d:body type="text/javascript"><![CDATA[
					// check the 'dataSource' attribute
					if (this.hasAttribute('dataSource')) {
						return btl.dataBinding.getSource(this.getAttribute('dataSource'));
					}
					// find the child which is derived from dataSource
					var aChildren = this.getProperty('childNodes');
					for (var i = 0, iLimit = aChildren.length; iLimit > i; i++) {
						if (bb.instanceOf(aChildren[i], btl.namespaceURI, 'dataSource') &&
								bb.instanceOf(aChildren[i], btl.namespaceURI, 'iDataCommunicator')) {
							return aChildren[i];
						}
					}
					// check if the element is a dataSource itself
					if (bb.instanceOf(this, btl.namespaceURI, 'dataSource') &&
							bb.instanceOf(this, btl.namespaceURI, 'iDataCommunicator'))
						return this;
					// or return null (not found)
					return null;
				]]></d:body>
			</d:method>

			<d:property name="indexes">
				
				<d:setter type="text/javascript"><![CDATA[
					this._['_' + name] = value;
					this._.oIndexes = {};
					for(var i = 0, iMax = value.length; iMax > i; i++)
						this._.oIndexes[value[i]] = true;
				]]></d:setter>
			</d:property>

			<d:property name="sorted">
				
				<d:setter type="text/javascript"><![CDATA[
					var oldValue = this._._sorted;
					this._._sorted = value;
					if (value)
						if(oldValue != value)
							bb.command.fireEvent(this, 'sort', false, false);
				]]></d:setter>
			</d:property>

			<d:property name="sortDirection">
				
				<d:setter type="text/javascript"><![CDATA[
					this.setAttribute('sortDirection', value);
				]]></d:setter>
				<d:getter type="text/javascript"><![CDATA[
					return this.getAttribute('sortDirection');
				]]></d:getter>
			</d:property>

			<d:property name="sortField">
				
				<d:setter type="text/javascript"><![CDATA[
					this.setAttribute(name, value);
				]]></d:setter>
				<d:getter type="text/javascript"><![CDATA[
					return this.getAttribute('sortField');
				]]></d:getter>
			</d:property>

			<d:property name="state">
				
				<d:getter type="text/javascript"><![CDATA[
					var sort_obj = this.getProperty('sortable') ?
							btl.dataSource.getObjectFromAttributes(this, ['sortDirection', 'sortField'], true) : {};
					var cur_obj = this._['_' + name];
					if (cur_obj){
						var new_obj = {};
						for(var key in cur_obj)
							new_obj[key] = cur_obj[key]
						for(var key in sort_obj)
							new_obj[key] = sort_obj[key]
						return new_obj;
					} else
						return sort_obj;
				]]></d:getter>
			</d:property>

			<d:property name="rows">
				
				<d:setter type="text/javascript"><![CDATA[
					this.setAttribute('rows', value);
				]]></d:setter>
				<d:getter type="text/javascript"><![CDATA[
					var iRows = parseInt(this.getAttribute('rows'), 10);
					if (isNaN(iRows)) {
						var oSource = this.getProperty('dataSource');
						return oSource ? oSource.getProperty('totalRecords') : 0;
					}
					return iRows;
				]]></d:getter>
			</d:property>

			<d:property name="sortable">
				
				<d:getter type="text/javascript"><![CDATA[
					return (this.getAttribute('sortField').length * this.getAttribute('sortDirection').length != 0);
				]]></d:getter>
			</d:property>

			<d:method name="dataUpdate">
				
				<d:argument name="action">
					
				</d:argument>
				<d:argument name="records">
					
				</d:argument>
				<d:argument name="actionObj">
					
				</d:argument>
				<d:body type="text/javascript"><![CDATA[
					// updates indexes by default
					var aIndexes = 	this.getProperty("indexes");
					switch (action){
						case "sort":
						case "read":
							for(var i = 0; records.length > i; i++)
								if (!this._.oIndexes[records[i]])
									aIndexes.push(records[i]);
							this.setProperty("indexes", aIndexes);
							break;
						case "delete":
							var oDeleted = {};
							for(var i = 0; records.length > i; i++)
								oDeleted[ records[i]] = true;

							for(var i = aIndexes.length - 1; i >= 0; i--)
								if (aIndexes[i] in oDeleted) aIndexes.splice(i, 1);
							this.setProperty("indexes", aIndexes);
					}
				]]></d:body>
			</d:method>

			<d:method name="refresh">
				
				<d:argument name="updateOnly">
					
				</d:argument>
				<d:body type="text/javascript"><![CDATA[
					btl.dataBinding.refreshObserver(this, updateOnly ? false : true);
				]]></d:body>
			</d:method>

			<d:method name="clear">
				
				<d:body type="text/javascript"><![CDATA[
					this.setProperty('indexes', []);
				]]></d:body>
			</d:method>

			<d:method name="sort">
				
				<d:argument name="sortField">
					
				</d:argument>
				<d:argument name="sortDirection">
					
				</d:argument>
				<d:body type="text/javascript"><![CDATA[
					this.setProperty('sorted', false);
					if (arguments.length && sortField != this.getAttribute('sortField')) //desorts if sortField is empty string or null
						this.setProperty('sortField', sortField ? sortField : '');

					if (sortDirection && (sortDirection != this.getAttribute('sortDirection')))
						this.setProperty('sortDirection', sortDirection == 'descending' ? 'descending' : 'ascending');

					btl.dataSource.actionRequest(this, 'sort', {'attributes' : this.getProperty('state')}, true);
				]]></d:body>
			</d:method>

			<d:handler event="DOMNodeInsertedIntoDocument" type="text/javascript"><![CDATA[
				this._._sorted = false;
				this._.bInitialized = true;
				if (!this._.oIndexes)
					this.setProperty('indexes', []);
				btl.dataBinding.dataSources.registerObserver(this);
			]]></d:handler>

			<d:handler event="DOMNodeRemovedFromDocument" type="text/javascript"><![CDATA[
				this._.bInitialized = false;
				btl.dataBinding.dataSources.unregisterObserver(this);
			]]></d:handler>
		</d:element>

		<!-- ############################################# dataPagedObserver ################################## -->
		<d:element name="dataPagedObserver" extends="b:dataObserver" implements="b:iPageable" abstract="true">
			

			<d:attribute name="page" default="1">
				
			</d:attribute>

			<d:property name="page">
				<d:setter type="text/javascript"><![CDATA[
					if (this.getProperty('page') != value){
						this.setAttribute('page', value);
						btl.dataBinding.refreshObserver(this, true);
					}
				]]></d:setter>
				<d:getter type="text/javascript"><![CDATA[
					if(this.getProperty("totalPages") > 0) {
						return parseInt(this.getAttribute('page'), 10);
					}
					else {
						return 0;
					}
				]]></d:getter>
			</d:property>

			<d:property name="totalPages">
				<d:getter type="text/javascript"><![CDATA[
					var oSource = this.getProperty('dataSource');
					var iPageSize = this.getProperty('rows');
					var aIndexes = this.getProperty('indexes');
					var iRecNum = aIndexes instanceof Array ? aIndexes.length : 1;
					var iTotalRows = oSource ? oSource.getProperty('totalRecords') : iRecNum;
					return iPageSize ? Math.ceil(iTotalRows / iPageSize) : 1;
				]]></d:getter>
			</d:property>

			<d:property name="state">
				
				<d:getter type="text/javascript"><![CDATA[
					var arr = ['page', 'rows'];
					if (this.getProperty('sortable')) {
						arr.push('sortDirection');
						arr.push('sortField');
					}
					var obj = btl.dataSource.getObjectFromAttributes(this, arr, true);
					var cur_obj = this._['_' + name];
					if (cur_obj) {
						var new_obj = {};
						for (var key in cur_obj)
							new_obj[key] = cur_obj[key]
						for (var key in obj)
							new_obj[key] = obj[key]
						obj = new_obj;
					}
					if (!(obj.rows == '' || isNaN(obj.rows)) &&
							!(obj.page == '' || isNaN(obj.page))) {
						var iPage = Math.max(obj.page, 1);
						obj.rangeStart = (iPage - 1) * obj.rows;
						obj.rangeEnd = iPage * obj.rows - 1;
						// rows and page are sent as well for backwards compatibility
					} else {
						delete obj.rows;	// don't send page and rows parameters
						delete obj.page;
					}
					return obj;
				]]></d:getter>
			</d:property>

			<d:method name="goTo">
				<d:argument name="page"/>
				<d:body type="text/javascript"><![CDATA[
					if (this.getProperty('dataSource')) {
						var iTotalPages = this.getProperty('totalPages');
						if (page > iTotalPages)
							page = iTotalPages;
						if (1 > page)
							page = 1;
						this.setProperty('page', page);
					}
				]]></d:body>
			</d:method>

			<d:method name="goNext">
				<d:body type="text/javascript"><![CDATA[
					var oSource = this.getProperty('dataSource');
					if (oSource) {
						var iPage = this.getProperty('page');
						var iRows = this.getProperty('rows');
						var iLast = oSource.getProperty('totalRecords');
						if (iLast > 0 && iLast > (++iPage - 1) * iRows)
							this.setProperty('page', iPage);
					}
				]]></d:body>
			</d:method>

			<d:method name="goPrevious">
				<d:body type="text/javascript"><![CDATA[
					if (this.getProperty('dataSource')) {
						var iPage = this.getProperty('page');
						if (iPage > 1)
							this.setProperty('page', --iPage);
					}
				]]></d:body>
			</d:method>
		</d:element>
	</d:namespace>
</d:tdl>